mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
275 lines
8.5 KiB
Swift
275 lines
8.5 KiB
Swift
//
|
|
// FileUtils.swift
|
|
// SimpleX (iOS)
|
|
//
|
|
// Created by JRoberts on 15.04.2022.
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import OSLog
|
|
import UIKit
|
|
|
|
let logger = Logger()
|
|
|
|
// image file size for complession
|
|
public let MAX_IMAGE_SIZE: Int64 = 261_120 // 255KB
|
|
|
|
public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2
|
|
|
|
public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2
|
|
|
|
public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023KB
|
|
|
|
public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1GB
|
|
|
|
public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max
|
|
|
|
public let MAX_FILE_SIZE_SMP: Int64 = 8000000
|
|
|
|
public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300)
|
|
|
|
let CHAT_DB: String = "_chat.db"
|
|
|
|
let AGENT_DB: String = "_agent.db"
|
|
|
|
private let CHAT_DB_BAK: String = "_chat.db.bak"
|
|
|
|
private let AGENT_DB_BAK: String = "_agent.db.bak"
|
|
|
|
public func getDocumentsDirectory() -> URL {
|
|
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
}
|
|
|
|
public func getGroupContainerDirectory() -> URL {
|
|
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_NAME)!
|
|
}
|
|
|
|
func getAppDirectory() -> URL {
|
|
dbContainerGroupDefault.get() == .group
|
|
? getGroupContainerDirectory()
|
|
: getDocumentsDirectory()
|
|
}
|
|
|
|
let DB_FILE_PREFIX = "simplex_v1"
|
|
|
|
func getLegacyDatabasePath() -> URL {
|
|
getDocumentsDirectory().appendingPathComponent("mobile_v1", isDirectory: false)
|
|
}
|
|
|
|
public func getAppDatabasePath() -> URL {
|
|
dbContainerGroupDefault.get() == .group
|
|
? getGroupContainerDirectory().appendingPathComponent(DB_FILE_PREFIX, isDirectory: false)
|
|
: getLegacyDatabasePath()
|
|
}
|
|
|
|
func fileModificationDate(_ path: String) -> Date? {
|
|
do {
|
|
let attr = try FileManager.default.attributesOfItem(atPath: path)
|
|
return attr[FileAttributeKey.modificationDate] as? Date
|
|
} catch {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func deleteAppDatabaseAndFiles() {
|
|
let fm = FileManager.default
|
|
let dbPath = getAppDatabasePath().path
|
|
do {
|
|
try fm.removeItem(atPath: dbPath + CHAT_DB)
|
|
try fm.removeItem(atPath: dbPath + AGENT_DB)
|
|
} catch let error {
|
|
logger.error("Failed to delete all databases: \(error)")
|
|
}
|
|
try? fm.removeItem(atPath: dbPath + CHAT_DB_BAK)
|
|
try? fm.removeItem(atPath: dbPath + AGENT_DB_BAK)
|
|
try? fm.removeItem(at: getTempFilesDirectory())
|
|
try? fm.removeItem(at: getMigrationTempFilesDirectory())
|
|
try? fm.createDirectory(at: getTempFilesDirectory(), withIntermediateDirectories: true)
|
|
try? fm.removeItem(at: getWallpaperDirectory())
|
|
try? fm.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
|
|
deleteAppFiles()
|
|
_ = kcDatabasePassword.remove()
|
|
storeDBPassphraseGroupDefault.set(true)
|
|
}
|
|
|
|
public func deleteAppFiles() {
|
|
let fm = FileManager.default
|
|
do {
|
|
try fm.removeItem(at: getAppFilesDirectory())
|
|
try fm.createDirectory(at: getAppFilesDirectory(), withIntermediateDirectories: true)
|
|
} catch {
|
|
logger.error("FileUtils deleteAppFiles error: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
public func fileSize(_ url: URL) -> Int? { // in bytes
|
|
do {
|
|
let val = try url.resourceValues(forKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey])
|
|
return val.totalFileAllocatedSize ?? val.fileAllocatedSize
|
|
} catch {
|
|
logger.error("FileUtils fileSize error: \(error.localizedDescription)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func directoryFileCountAndSize(_ dir: URL) -> (Int, Int)? { // size in bytes
|
|
let fm = FileManager.default
|
|
if let enumerator = fm.enumerator(at: dir, includingPropertiesForKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey], options: [], errorHandler: { (_, error) -> Bool in
|
|
logger.error("FileUtils directoryFileCountAndSize error: \(error.localizedDescription)")
|
|
return false
|
|
}) {
|
|
var fileCount = 0
|
|
var bytes = 0
|
|
for case let url as URL in enumerator {
|
|
fileCount += 1
|
|
bytes += fileSize(url) ?? 0
|
|
}
|
|
return (fileCount, bytes)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func hasBackup(newerThan date: Date) -> Bool {
|
|
let dbPath = getAppDatabasePath().path
|
|
return hasBackupFile(dbPath + AGENT_DB_BAK, newerThan: date)
|
|
&& hasBackupFile(dbPath + CHAT_DB_BAK, newerThan: date)
|
|
}
|
|
|
|
private func hasBackupFile(_ path: String, newerThan date: Date) -> Bool {
|
|
let fm = FileManager.default
|
|
return fm.fileExists(atPath: path)
|
|
&& date <= (fileModificationDate(path) ?? Date.distantPast)
|
|
}
|
|
|
|
public func restoreBackup() throws {
|
|
let dbPath = getAppDatabasePath().path
|
|
try restoreBackupFile(fromPath: dbPath + AGENT_DB_BAK, toPath: dbPath + AGENT_DB)
|
|
try restoreBackupFile(fromPath: dbPath + CHAT_DB_BAK, toPath: dbPath + CHAT_DB)
|
|
}
|
|
|
|
private func restoreBackupFile(fromPath: String, toPath: String) throws {
|
|
let fm = FileManager.default
|
|
if fm.fileExists(atPath: toPath) {
|
|
try fm.removeItem(atPath: toPath)
|
|
}
|
|
try fm.copyItem(atPath: fromPath, toPath: toPath)
|
|
}
|
|
|
|
public func hasLegacyDatabase() -> Bool {
|
|
hasDatabaseAtPath(getLegacyDatabasePath())
|
|
}
|
|
|
|
public func hasDatabase() -> Bool {
|
|
hasDatabaseAtPath(getAppDatabasePath())
|
|
}
|
|
|
|
func hasDatabaseAtPath(_ dbPath: URL) -> Bool {
|
|
let fm = FileManager.default
|
|
return fm.isReadableFile(atPath: dbPath.path + AGENT_DB) &&
|
|
fm.isReadableFile(atPath: dbPath.path + CHAT_DB)
|
|
}
|
|
|
|
public func removeLegacyDatabaseAndFiles() -> Bool {
|
|
let dbPath = getLegacyDatabasePath()
|
|
let appFiles = getDocumentsDirectory().appendingPathComponent("app_files", isDirectory: true)
|
|
let fm = FileManager.default
|
|
let r1 = nil != (try? fm.removeItem(atPath: dbPath.path + AGENT_DB))
|
|
let r2 = nil != (try? fm.removeItem(atPath: dbPath.path + CHAT_DB))
|
|
try? fm.removeItem(atPath: dbPath.path + AGENT_DB_BAK)
|
|
try? fm.removeItem(atPath: dbPath.path + CHAT_DB_BAK)
|
|
try? fm.removeItem(at: appFiles)
|
|
return r1 && r2
|
|
}
|
|
|
|
public func getTempFilesDirectory() -> URL {
|
|
getAppDirectory().appendingPathComponent("temp_files", isDirectory: true)
|
|
}
|
|
|
|
public func getMigrationTempFilesDirectory() -> URL {
|
|
getDocumentsDirectory().appendingPathComponent("migration_temp_files", isDirectory: true)
|
|
}
|
|
|
|
public func getAppFilesDirectory() -> URL {
|
|
getAppDirectory().appendingPathComponent("app_files", isDirectory: true)
|
|
}
|
|
|
|
public func getAppFilePath(_ fileName: String) -> URL {
|
|
getAppFilesDirectory().appendingPathComponent(fileName)
|
|
}
|
|
|
|
public func getWallpaperDirectory() -> URL {
|
|
getAppDirectory().appendingPathComponent("assets", isDirectory: true).appendingPathComponent("wallpapers", isDirectory: true)
|
|
}
|
|
|
|
public func getWallpaperFilePath(_ filename: String) -> URL {
|
|
getWallpaperDirectory().appendingPathComponent(filename)
|
|
}
|
|
|
|
public func saveFile(_ data: Data, _ fileName: String, encrypted: Bool) -> CryptoFile? {
|
|
let filePath = getAppFilePath(fileName)
|
|
do {
|
|
if encrypted {
|
|
let cfArgs = try writeCryptoFile(path: filePath.path, data: data)
|
|
return CryptoFile(filePath: fileName, cryptoArgs: cfArgs)
|
|
} else {
|
|
try data.write(to: filePath)
|
|
return CryptoFile.plain(fileName)
|
|
}
|
|
} catch {
|
|
logger.error("FileUtils.saveFile error: \(error.localizedDescription)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func removeFile(_ url: URL) {
|
|
do {
|
|
try FileManager.default.removeItem(atPath: url.path)
|
|
} catch {
|
|
logger.error("FileUtils.removeFile error: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
public func removeFile(_ fileName: String) {
|
|
do {
|
|
try FileManager.default.removeItem(atPath: getAppFilePath(fileName).path)
|
|
} catch {
|
|
logger.error("FileUtils.removeFile error: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
public func cleanupDirectFile(_ aChatItem: AChatItem) {
|
|
if aChatItem.chatInfo.chatType == .direct {
|
|
cleanupFile(aChatItem)
|
|
}
|
|
}
|
|
|
|
public func cleanupFile(_ aChatItem: AChatItem) {
|
|
let cItem = aChatItem.chatItem
|
|
let mc = cItem.content.msgContent
|
|
if case .file = mc,
|
|
let fileName = cItem.file?.fileSource?.filePath {
|
|
removeFile(fileName)
|
|
}
|
|
}
|
|
|
|
public func getMaxFileSize(_ fileProtocol: FileProtocol) -> Int64 {
|
|
switch fileProtocol {
|
|
case .xftp: return MAX_FILE_SIZE_XFTP
|
|
case .smp: return MAX_FILE_SIZE_SMP
|
|
case .local: return MAX_FILE_SIZE_LOCAL
|
|
}
|
|
}
|
|
|
|
public struct RuntimeError: Error {
|
|
let message: String
|
|
|
|
public init(_ message: String) {
|
|
self.message = message
|
|
}
|
|
|
|
public var localizedDescription: String {
|
|
return message
|
|
}
|
|
}
|