mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
14 KiB
Swift
332 lines
14 KiB
Swift
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
|
//
|
|
// stringlint:disable
|
|
|
|
import Foundation
|
|
|
|
// MARK: - Singleton
|
|
|
|
public extension Singleton {
|
|
static let fileManager: SingletonConfig<FileManagerType> = Dependencies.create(
|
|
identifier: "fileManager",
|
|
createInstance: { dependencies in SessionFileManager(using: dependencies) }
|
|
)
|
|
}
|
|
|
|
// MARK: - FileManagerType
|
|
|
|
public protocol FileManagerType {
|
|
var temporaryDirectory: String { get }
|
|
var appSharedDataDirectoryPath: String { get }
|
|
var temporaryDirectoryAccessibleAfterFirstAuth: String { get }
|
|
|
|
/// **Note:** We need to call this method on launch _and_ every time the app becomes active,
|
|
/// since file protection may prevent it from succeeding in the background.
|
|
func clearOldTemporaryDirectories()
|
|
|
|
func ensureDirectoryExists(at path: String, fileProtectionType: FileProtectionType) throws
|
|
func protectFileOrFolder(at path: String, fileProtectionType: FileProtectionType) throws
|
|
func fileSize(of path: String) -> UInt64?
|
|
func temporaryFilePath(fileExtension: String?) -> String
|
|
func write(data: Data, toTemporaryFileWithExtension fileExtension: String?) throws -> String?
|
|
|
|
// MARK: - Forwarded NSFileManager
|
|
|
|
var currentDirectoryPath: String { get }
|
|
|
|
func urls(for directory: FileManager.SearchPathDirectory, in domains: FileManager.SearchPathDomainMask) -> [URL]
|
|
func enumerator(
|
|
at url: URL,
|
|
includingPropertiesForKeys keys: [URLResourceKey]?,
|
|
options: FileManager.DirectoryEnumerationOptions,
|
|
errorHandler: ((URL, Error) -> Bool)?
|
|
) -> FileManager.DirectoryEnumerator?
|
|
|
|
func fileExists(atPath: String) -> Bool
|
|
func fileExists(atPath: String, isDirectory: UnsafeMutablePointer<ObjCBool>?) -> Bool
|
|
func contents(atPath: String) -> Data?
|
|
func contentsOfDirectory(at url: URL) throws -> [URL]
|
|
func contentsOfDirectory(atPath path: String) throws -> [String]
|
|
|
|
func createFile(atPath: String, contents: Data?, attributes: [FileAttributeKey: Any]?) -> Bool
|
|
func createDirectory(at url: URL, withIntermediateDirectories: Bool, attributes: [FileAttributeKey: Any]?) throws
|
|
func createDirectory(atPath: String, withIntermediateDirectories: Bool, attributes: [FileAttributeKey: Any]?) throws
|
|
func copyItem(atPath: String, toPath: String) throws
|
|
func copyItem(at fromUrl: URL, to toUrl: URL) throws
|
|
func removeItem(atPath: String) throws
|
|
|
|
func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: Any]
|
|
func setAttributes(_ attributes: [FileAttributeKey: Any], ofItemAtPath path: String) throws
|
|
}
|
|
|
|
public extension FileManagerType {
|
|
func ensureDirectoryExists(at path: String) throws {
|
|
try ensureDirectoryExists(at: path, fileProtectionType: .completeUntilFirstUserAuthentication)
|
|
}
|
|
|
|
func protectFileOrFolder(at path: String) throws {
|
|
try protectFileOrFolder(at: path, fileProtectionType: .completeUntilFirstUserAuthentication)
|
|
}
|
|
|
|
func enumerator(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?) -> FileManager.DirectoryEnumerator? {
|
|
return enumerator(at: url, includingPropertiesForKeys: keys, options: [], errorHandler: nil)
|
|
}
|
|
|
|
func enumerator(
|
|
at url: URL,
|
|
includingPropertiesForKeys keys: [URLResourceKey]?,
|
|
options: FileManager.DirectoryEnumerationOptions
|
|
) -> FileManager.DirectoryEnumerator? {
|
|
return enumerator(at: url, includingPropertiesForKeys: keys, options: options, errorHandler: nil)
|
|
}
|
|
|
|
func enumerator(
|
|
at url: URL,
|
|
includingPropertiesForKeys keys: [URLResourceKey]?,
|
|
errorHandler: ((URL, Error) -> Bool)?
|
|
) -> FileManager.DirectoryEnumerator? {
|
|
return enumerator(at: url, includingPropertiesForKeys: keys, options: [], errorHandler: errorHandler)
|
|
}
|
|
|
|
func createFile(atPath: String, contents: Data?) -> Bool {
|
|
return createFile(atPath: atPath, contents: contents, attributes: nil)
|
|
}
|
|
|
|
func createDirectory(at url: URL, withIntermediateDirectories: Bool) throws {
|
|
try createDirectory(at: url, withIntermediateDirectories: withIntermediateDirectories, attributes: nil)
|
|
}
|
|
|
|
func createDirectory(atPath: String, withIntermediateDirectories: Bool) throws {
|
|
try createDirectory(atPath: atPath, withIntermediateDirectories: withIntermediateDirectories, attributes: nil)
|
|
}
|
|
}
|
|
|
|
// MARK: - Convenience
|
|
|
|
public extension SessionFileManager {
|
|
static var cachesDirectoryPath: String {
|
|
return NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]
|
|
}
|
|
|
|
static var nonInjectedAppSharedDataDirectoryPath: String {
|
|
return (FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: UserDefaults.applicationGroup)?
|
|
.path)
|
|
.defaulting(to: "")
|
|
}
|
|
}
|
|
|
|
// MARK: - SessionFileManager
|
|
|
|
public class SessionFileManager: FileManagerType {
|
|
private let dependencies: Dependencies
|
|
private let fileManager: FileManager = .default
|
|
public var temporaryDirectory: String
|
|
|
|
public var appSharedDataDirectoryPath: String {
|
|
return (fileManager.containerURL(forSecurityApplicationGroupIdentifier: UserDefaults.applicationGroup)?.path)
|
|
.defaulting(to: "")
|
|
}
|
|
|
|
public var temporaryDirectoryAccessibleAfterFirstAuth: String {
|
|
let dirPath: String = NSTemporaryDirectory()
|
|
try? ensureDirectoryExists(at: dirPath, fileProtectionType: .completeUntilFirstUserAuthentication)
|
|
|
|
return dirPath
|
|
}
|
|
|
|
// MARK: - Initialization
|
|
|
|
init(using dependencies: Dependencies) {
|
|
self.dependencies = dependencies
|
|
|
|
// Create a new temp directory for this instance
|
|
let dirName: String = "ows_temp_\(UUID().uuidString)"
|
|
self.temporaryDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
|
|
.appendingPathComponent(dirName)
|
|
.path
|
|
try? ensureDirectoryExists(at: self.temporaryDirectory, fileProtectionType: .complete)
|
|
}
|
|
|
|
// MARK: - Functions
|
|
|
|
public func clearOldTemporaryDirectories() {
|
|
// We use the lowest priority queue for this, and wait N seconds
|
|
// to avoid interfering with app startup.
|
|
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds(3), using: dependencies) { [temporaryDirectory, fileManager, dependencies] in
|
|
// Abort if app not active
|
|
guard dependencies[singleton: .appContext].isAppForegroundAndActive else { return }
|
|
|
|
// Ignore the "current" temp directory.
|
|
let thresholdDate: Date = dependencies[singleton: .appContext].appLaunchTime
|
|
let currentTempDirName: String = URL(fileURLWithPath: temporaryDirectory).lastPathComponent
|
|
let dirPath: String = NSTemporaryDirectory()
|
|
|
|
guard let fileNames: [String] = try? fileManager.contentsOfDirectory(atPath: dirPath) else { return }
|
|
|
|
fileNames.forEach { fileName in
|
|
guard fileName != currentTempDirName else { return }
|
|
|
|
// Delete files with either:
|
|
//
|
|
// a) "ows_temp" name prefix.
|
|
// b) modified time before app launch time.
|
|
let filePath: String = URL(fileURLWithPath: dirPath).appendingPathComponent(fileName).path
|
|
|
|
if !fileName.hasPrefix("ows_temp") {
|
|
// It's fine if we can't get the attributes (the file may have been deleted since we found it),
|
|
// also don't delete files which were created in the last N minutes
|
|
guard
|
|
let attributes: [FileAttributeKey: Any] = try? fileManager.attributesOfItem(atPath: filePath),
|
|
let modificationDate: Date = attributes[.modificationDate] as? Date,
|
|
modificationDate.timeIntervalSince1970 <= thresholdDate.timeIntervalSince1970
|
|
else { return }
|
|
}
|
|
|
|
// This can happen if the app launches before the phone is unlocked.
|
|
// Clean up will occur when app becomes active.
|
|
try? fileManager.removeItem(atPath: filePath)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func ensureDirectoryExists(at path: String, fileProtectionType: FileProtectionType) throws {
|
|
var isDirectory: ObjCBool = false
|
|
|
|
if !fileManager.fileExists(atPath: path, isDirectory: &isDirectory) {
|
|
try fileManager.createDirectory(
|
|
atPath: path,
|
|
withIntermediateDirectories: true,
|
|
attributes: nil
|
|
)
|
|
}
|
|
|
|
try protectFileOrFolder(at: path, fileProtectionType: fileProtectionType)
|
|
}
|
|
|
|
public func protectFileOrFolder(at path: String, fileProtectionType: FileProtectionType) throws {
|
|
guard fileManager.fileExists(atPath: path) else { return }
|
|
|
|
try fileManager.setAttributes(
|
|
[.protectionKey: fileProtectionType],
|
|
ofItemAtPath: path
|
|
)
|
|
|
|
var resourcesUrl: URL = URL(fileURLWithPath: path)
|
|
var resourceAttrs: URLResourceValues = URLResourceValues()
|
|
resourceAttrs.isExcludedFromBackup = true
|
|
try resourcesUrl.setResourceValues(resourceAttrs)
|
|
}
|
|
|
|
public func fileSize(of path: String) -> UInt64? {
|
|
guard let attributes: [FileAttributeKey: Any] = try? fileManager.attributesOfItem(atPath: path) else {
|
|
return nil
|
|
}
|
|
|
|
return (attributes[.size] as? UInt64)
|
|
}
|
|
|
|
public func temporaryFilePath(fileExtension: String?) -> String {
|
|
var tempFileName: String = UUID().uuidString
|
|
|
|
if let fileExtension: String = fileExtension, !fileExtension.isEmpty {
|
|
tempFileName = "\(tempFileName).\(fileExtension)"
|
|
}
|
|
|
|
return URL(fileURLWithPath: temporaryDirectory)
|
|
.appendingPathComponent(tempFileName)
|
|
.path
|
|
}
|
|
|
|
public func write(data: Data, toTemporaryFileWithExtension fileExtension: String?) throws -> String? {
|
|
let tempFilePath: String = temporaryFilePath(fileExtension: fileExtension)
|
|
|
|
try data.write(to: URL(fileURLWithPath: tempFilePath), options: .atomic)
|
|
try protectFileOrFolder(at: tempFilePath)
|
|
|
|
return tempFilePath
|
|
}
|
|
|
|
// MARK: - Forwarded NSFileManager
|
|
|
|
public var currentDirectoryPath: String { fileManager.currentDirectoryPath }
|
|
|
|
public func urls(for directory: FileManager.SearchPathDirectory, in domains: FileManager.SearchPathDomainMask) -> [URL] {
|
|
|
|
return fileManager.urls(for: directory, in: domains)
|
|
}
|
|
|
|
public func enumerator(
|
|
at url: URL,
|
|
includingPropertiesForKeys: [URLResourceKey]?,
|
|
options: FileManager.DirectoryEnumerationOptions,
|
|
errorHandler: ((URL, Error) -> Bool)?
|
|
) -> FileManager.DirectoryEnumerator? {
|
|
return fileManager.enumerator(
|
|
at: url,
|
|
includingPropertiesForKeys: includingPropertiesForKeys,
|
|
options: options,
|
|
errorHandler: errorHandler
|
|
)
|
|
}
|
|
|
|
public func fileExists(atPath: String) -> Bool {
|
|
return fileManager.fileExists(atPath: atPath)
|
|
}
|
|
|
|
public func fileExists(atPath: String, isDirectory: UnsafeMutablePointer<ObjCBool>?) -> Bool {
|
|
return fileManager.fileExists(atPath: atPath, isDirectory: isDirectory)
|
|
}
|
|
|
|
public func contents(atPath: String) -> Data? {
|
|
return fileManager.contents(atPath: atPath)
|
|
}
|
|
|
|
public func contentsOfDirectory(at url: URL) throws -> [URL] {
|
|
return try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
|
|
}
|
|
|
|
public func contentsOfDirectory(atPath path: String) throws -> [String] {
|
|
return try fileManager.contentsOfDirectory(atPath: path)
|
|
}
|
|
|
|
public func createFile(atPath: String, contents: Data?, attributes: [FileAttributeKey: Any]?) -> Bool {
|
|
return fileManager.createFile(atPath: atPath, contents: contents, attributes: attributes)
|
|
}
|
|
|
|
public func createDirectory(at url: URL, withIntermediateDirectories: Bool, attributes: [FileAttributeKey: Any]?) throws {
|
|
return try fileManager.createDirectory(
|
|
at: url,
|
|
withIntermediateDirectories: withIntermediateDirectories,
|
|
attributes: attributes
|
|
)
|
|
}
|
|
|
|
public func createDirectory(atPath: String, withIntermediateDirectories: Bool, attributes: [FileAttributeKey: Any]?) throws {
|
|
return try fileManager.createDirectory(
|
|
atPath: atPath,
|
|
withIntermediateDirectories: withIntermediateDirectories,
|
|
attributes: attributes
|
|
)
|
|
}
|
|
|
|
public func copyItem(atPath: String, toPath: String) throws {
|
|
return try fileManager.copyItem(atPath: atPath, toPath: toPath)
|
|
}
|
|
|
|
public func copyItem(at fromUrl: URL, to toUrl: URL) throws {
|
|
return try fileManager.copyItem(at: fromUrl, to: toUrl)
|
|
}
|
|
|
|
public func removeItem(atPath: String) throws {
|
|
return try fileManager.removeItem(atPath: atPath)
|
|
}
|
|
|
|
public func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: Any] {
|
|
return try fileManager.attributesOfItem(atPath: path)
|
|
}
|
|
|
|
public func setAttributes(_ attributes: [FileAttributeKey: Any], ofItemAtPath path: String) throws {
|
|
return try fileManager.setAttributes(attributes, ofItemAtPath: path)
|
|
}
|
|
}
|