Disk+Helpers.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import Foundation
  2. public extension Disk {
  3. /// Get URL for existing file
  4. ///
  5. /// - Parameters:
  6. /// - path: path of file relative to directory (set nil for entire directory)
  7. /// - directory: directory the file is saved in
  8. /// - Returns: URL pointing to file
  9. /// - Throws: Error if no file could be found
  10. @available(
  11. *,
  12. deprecated,
  13. message: "Use Disk.url(for:in:) instead, it does not throw an error if the file does not exist."
  14. ) static func getURL(for path: String?, in directory: Directory) throws -> URL {
  15. do {
  16. let url = try getExistingFileURL(for: path, in: directory)
  17. return url
  18. } catch {
  19. throw error
  20. }
  21. }
  22. /// Construct URL for a potentially existing or non-existent file (Note: replaces `getURL(for:in:)` which would throw an error if file does not exist)
  23. ///
  24. /// - Parameters:
  25. /// - path: path of file relative to directory (set nil for entire directory)
  26. /// - directory: directory for the specified path
  27. /// - Returns: URL for either an existing or non-existing file
  28. /// - Throws: Error if URL creation failed
  29. static func url(for path: String?, in directory: Directory) throws -> URL {
  30. do {
  31. let url = try createURL(for: path, in: directory)
  32. return url
  33. } catch {
  34. throw error
  35. }
  36. }
  37. /// Clear directory by removing all files
  38. ///
  39. /// - Parameter directory: directory to clear
  40. /// - Throws: Error if FileManager cannot access a directory
  41. static func clear(_ directory: Directory) throws {
  42. do {
  43. let url = try createURL(for: nil, in: directory)
  44. let contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
  45. for fileUrl in contents {
  46. try? FileManager.default.removeItem(at: fileUrl)
  47. }
  48. } catch {
  49. throw error
  50. }
  51. }
  52. /// Remove file from the file system
  53. ///
  54. /// - Parameters:
  55. /// - path: path of file relative to directory
  56. /// - directory: directory where file is located
  57. /// - Throws: Error if file could not be removed
  58. static func remove(_ path: String, from directory: Directory) throws {
  59. do {
  60. let url = try getExistingFileURL(for: path, in: directory)
  61. try FileManager.default.removeItem(at: url)
  62. } catch {
  63. throw error
  64. }
  65. }
  66. /// Remove file from the file system
  67. ///
  68. /// - Parameters:
  69. /// - url: URL of file in filesystem
  70. /// - Throws: Error if file could not be removed
  71. static func remove(_ url: URL) throws {
  72. do {
  73. try FileManager.default.removeItem(at: url)
  74. } catch {
  75. throw error
  76. }
  77. }
  78. /// Checks if a file exists
  79. ///
  80. /// - Parameters:
  81. /// - path: path of file relative to directory
  82. /// - directory: directory where file is located
  83. /// - Returns: Bool indicating whether file exists
  84. static func exists(_ path: String, in directory: Directory) -> Bool {
  85. if let _ = try? getExistingFileURL(for: path, in: directory) {
  86. return true
  87. }
  88. return false
  89. }
  90. /// Checks if a file exists
  91. ///
  92. /// - Parameters:
  93. /// - url: URL of file in filesystem
  94. /// - Returns: Bool indicating whether file exists
  95. static func exists(_ url: URL) -> Bool {
  96. if FileManager.default.fileExists(atPath: url.path) {
  97. return true
  98. }
  99. return false
  100. }
  101. /// Sets the 'do not backup' attribute of the file or folder on disk to true. This ensures that the file holding the object data does not get deleted when the user's device has low storage, but prevents this file from being stored in any backups made of the device on iTunes or iCloud.
  102. /// This is only useful for excluding cache and other application support files which are not needed in a backup. Some operations commonly made to user documents will cause the 'do not backup' property to be reset to false and so this should not be used on user documents.
  103. /// Warning: You must ensure that you will purge and handle any files created with this attribute appropriately, as these files will persist on the user's disk even in low storage situtations. If you don't handle these files appropriately, then you aren't following Apple's file system guidlines and can face App Store rejection.
  104. /// Ideally, you should let iOS handle deletion of files in low storage situations, and you yourself handle missing files appropriately (i.e. retrieving an image from the web again if it does not exist on disk anymore.)
  105. ///
  106. /// - Parameters:
  107. /// - path: path of file relative to directory
  108. /// - directory: directory where file is located
  109. /// - Throws: Error if file could not set its 'isExcludedFromBackup' property
  110. static func doNotBackup(_ path: String, in directory: Directory) throws {
  111. do {
  112. try setIsExcludedFromBackup(to: true, for: path, in: directory)
  113. } catch {
  114. throw error
  115. }
  116. }
  117. /// Sets the 'do not backup' attribute of the file or folder on disk to true. This ensures that the file holding the object data does not get deleted when the user's device has low storage, but prevents this file from being stored in any backups made of the device on iTunes or iCloud.
  118. /// This is only useful for excluding cache and other application support files which are not needed in a backup. Some operations commonly made to user documents will cause the 'do not backup' property to be reset to false and so this should not be used on user documents.
  119. /// Warning: You must ensure that you will purge and handle any files created with this attribute appropriately, as these files will persist on the user's disk even in low storage situtations. If you don't handle these files appropriately, then you aren't following Apple's file system guidlines and can face App Store rejection.
  120. /// Ideally, you should let iOS handle deletion of files in low storage situations, and you yourself handle missing files appropriately (i.e. retrieving an image from the web again if it does not exist on disk anymore.)
  121. ///
  122. /// - Parameters:
  123. /// - url: URL of file in filesystem
  124. /// - Throws: Error if file could not set its 'isExcludedFromBackup' property
  125. static func doNotBackup(_ url: URL) throws {
  126. do {
  127. try setIsExcludedFromBackup(to: true, for: url)
  128. } catch {
  129. throw error
  130. }
  131. }
  132. /// Sets the 'do not backup' attribute of the file or folder on disk to false. This is the default behaviour so you don't have to use this function unless you already called doNotBackup(name:directory:) on a specific file.
  133. /// This default backing up behaviour allows anything in the .documents and .caches directories to be stored in backups made of the user's device (on iCloud or iTunes)
  134. ///
  135. /// - Parameters:
  136. /// - path: path of file relative to directory
  137. /// - directory: directory where file is located
  138. /// - Throws: Error if file could not set its 'isExcludedFromBackup' property
  139. static func backup(_ path: String, in directory: Directory) throws {
  140. do {
  141. try setIsExcludedFromBackup(to: false, for: path, in: directory)
  142. } catch {
  143. throw error
  144. }
  145. }
  146. /// Sets the 'do not backup' attribute of the file or folder on disk to false. This is the default behaviour so you don't have to use this function unless you already called doNotBackup(name:directory:) on a specific file.
  147. /// This default backing up behaviour allows anything in the .documents and .caches directories to be stored in backups made of the user's device (on iCloud or iTunes)
  148. ///
  149. /// - Parameters:
  150. /// - url: URL of file in filesystem
  151. /// - Throws: Error if file could not set its 'isExcludedFromBackup' property
  152. static func backup(_ url: URL) throws {
  153. do {
  154. try setIsExcludedFromBackup(to: false, for: url)
  155. } catch {
  156. throw error
  157. }
  158. }
  159. /// Move file to a new directory
  160. ///
  161. /// - Parameters:
  162. /// - path: path of file relative to directory
  163. /// - directory: directory the file is currently in
  164. /// - newDirectory: new directory to store file in
  165. /// - Throws: Error if file could not be moved
  166. static func move(_ path: String, in directory: Directory, to newDirectory: Directory) throws {
  167. do {
  168. let currentUrl = try getExistingFileURL(for: path, in: directory)
  169. let justDirectoryPath = try createURL(for: nil, in: directory).absoluteString
  170. let filePath = currentUrl.absoluteString.replacingOccurrences(of: justDirectoryPath, with: "")
  171. let newUrl = try createURL(for: filePath, in: newDirectory)
  172. try createSubfoldersBeforeCreatingFile(at: newUrl)
  173. try FileManager.default.moveItem(at: currentUrl, to: newUrl)
  174. } catch {
  175. throw error
  176. }
  177. }
  178. /// Move file to a new directory
  179. ///
  180. /// - Parameters:
  181. /// - path: path of file relative to directory
  182. /// - directory: directory the file is currently in
  183. /// - newDirectory: new directory to store file in
  184. /// - Throws: Error if file could not be moved
  185. static func move(_ originalURL: URL, to newURL: URL) throws {
  186. do {
  187. try createSubfoldersBeforeCreatingFile(at: newURL)
  188. try FileManager.default.moveItem(at: originalURL, to: newURL)
  189. } catch {
  190. throw error
  191. }
  192. }
  193. /// Rename a file
  194. ///
  195. /// - Parameters:
  196. /// - path: path of file relative to directory
  197. /// - directory: directory the file is in
  198. /// - newName: new name to give to file
  199. /// - Throws: Error if object could not be renamed
  200. static func rename(_ path: String, in directory: Directory, to newPath: String) throws {
  201. do {
  202. let currentUrl = try getExistingFileURL(for: path, in: directory)
  203. let justDirectoryPath = try createURL(for: nil, in: directory).absoluteString
  204. var currentFilePath = currentUrl.absoluteString.replacingOccurrences(of: justDirectoryPath, with: "")
  205. if isFolder(currentUrl), currentFilePath.suffix(1) != "/" {
  206. currentFilePath = currentFilePath + "/"
  207. }
  208. let currentValidFilePath = try getValidFilePath(from: path)
  209. let newValidFilePath = try getValidFilePath(from: newPath)
  210. let newFilePath = currentFilePath.replacingOccurrences(of: currentValidFilePath, with: newValidFilePath)
  211. let newUrl = try createURL(for: newFilePath, in: directory)
  212. try createSubfoldersBeforeCreatingFile(at: newUrl)
  213. try FileManager.default.moveItem(at: currentUrl, to: newUrl)
  214. } catch {
  215. throw error
  216. }
  217. }
  218. /// Check if file at a URL is a folder
  219. static func isFolder(_ url: URL) -> Bool {
  220. var isDirectory: ObjCBool = false
  221. if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
  222. if isDirectory.boolValue {
  223. return true
  224. }
  225. }
  226. return false
  227. }
  228. }