Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Threema/FileUtility/Sources/FileUtility/   (Social Medium ©)  Datei vom 26.9.2026 mit Größe 21 kB image not shown  

Quelle  FileUtility.swift   Sprache: JAVA

 

import CocoaLumberjackSwift
import Foundation

public final class FileUtility: NSObject, FileUtilityProtocol {
    
    public private(set) nonisolated(unsafe) static var shared: FileUtilityProtocol!
    
    public static func updateSharedInstance(with fileUtility: FileUtilityProtocol) {
        shared = fileUtility
    }

    private let fileManager: FileManager

    private lazy var filenameDateFormatter: DateFormatter = {
        let filenameDateFormatter = DateFormatter()
        // Always use this locale for locale independent formats (see https://nsdateformatter.com)
        filenameDateFormatter.locale = Locale(identifier: "en_US_POSIX")
        filenameDateFormatter.dateFormat = "yyyyMMdd-HHmmss-SSS"
        return filenameDateFormatter
    }()
    
    @objc public let appDocumentsDirectory: URL?

    public let appCachesDirectory: URL?

    /// Temporary app directory
    ///
    /// Please remove data stored here that is no longer needed: https://stackoverflow.com/a/25067497
    public let appTemporaryDirectory: URL
    
    /// Temporary app directory for unencrypted files.
    ///
    /// The directory is created on access if it is missing. Do not cache the returned URL: several routines wipe
    /// `tmp/` wholesale and take this directory with them, `removeItemsInDirectory(directoryURL:)` on
    /// `appTemporaryDirectory` and `cleanTemporaryDirectory(olderThan:)`, which lists top-level entries with
    /// `.skipsSubdirectoryDescendants`, among them.
    @objc public var appTemporaryUnencryptedDirectory: URL {
        createDirectoryIfNeeded(at: temporaryUnencryptedDirectory)
        return temporaryUnencryptedDirectory
    }

    /// Location of `appTemporaryUnencryptedDirectory`. Use that property, it also creates the directory.
    private let temporaryUnencryptedDirectory: URL
    
    @objc override public convenience init() {
        self.init(resolver: FileManagerResolver())
    }
    
    init(resolver: FileManagerResolverProtocol) {
        let fileManager = resolver.fileManager
        self.fileManager = fileManager
        self.appTemporaryDirectory = fileManager.temporaryDirectory
        self.temporaryUnencryptedDirectory =
            fileManager.temporaryDirectory.appendingPathComponent("unencrypted")
        self.appDocumentsDirectory = fileManager.urls(
            for: .documentDirectory,
            in: .userDomainMask
        ).last
        self.appCachesDirectory = fileManager.urls(
            for: .cachesDirectory,
            in: .userDomainMask
        ).last
        
        super.init()
        
        createDirectoryIfNeeded(at: temporaryUnencryptedDirectory)
    }

    public func appDataDirectory(appGroupID: String) -> URL? {
        fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)
    }
    
    // MARK: Disk space and calculation sizes of resources

    public func freeDiskSpaceInBytes() -> Int64 {
        var capacityInBytes: Int64?

        do {
            let fileURL = URL(fileURLWithPath: NSHomeDirectory())
            let values = try fileURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
            capacityInBytes = values.volumeAvailableCapacityForImportantUsage
        }
        catch {
            DDLogError("Cannot retrieve free disk space: \(error)")
        }

        return capacityInBytes ?? 0
    }

    public func pathSizeInBytes(pathURL: URL, size: inout Int64) {
        do {
            let resourceKeys: [URLResourceKey] = [.creationDateKey, .isDirectoryKey]

            if let urls = fileManager.enumerator(at: pathURL, includingPropertiesForKeys: resourceKeys) {
                for case let url as URL in urls {
                    let resourceValues = try url.resourceValues(forKeys: Set(resourceKeys))

                    if let isDirectory = resourceValues.isDirectory,
                       !isDirectory {

                        if let fileSize = fileSizeInBytes(fileURL: url) {
                            size = size + fileSize
                        }
                    }
                }
            }
        }
        catch {
            DDLogError("Failed to calculate size of \(pathURL.path): \(error)")
        }
    }

    public func fileSizeInBytes(fileURL: URL) -> Int64? {
        do {
            let fileAttr = try fileManager.attributesOfItem(atPath: fileURL.path)
            return fileAttr[FileAttributeKey.size] as? Int64
        }
        catch {
            DDLogError("\(error)")
        }

        return nil
    }

    public func getFileSizeDescription(for fileURL: URL) -> String? {
        guard let fileSize = fileSizeInBytes(fileURL: fileURL) else {
            return nil
        }
        return getFileSizeDescription(from: fileSize)
    }

    @objc public func getFileSizeDescription(from fileSize: Int64) -> String {
        ByteCountFormatter.string(fromByteCount: fileSize, countStyle: .binary)
    }

    // MARK: Generate file names

    public func getTemporaryFileName() -> String {
        var filename = ProcessInfo().globallyUniqueString
        let url = fileManager.temporaryDirectory
        var fileURL = url.appendingPathComponent(filename)

        while fileExists(at: fileURL) {
            filename = ProcessInfo().globallyUniqueString
            fileURL = url.appendingPathComponent(filename)
        }
        return filename
    }

    @objc public func getTemporarySendableFileName(base: String) -> String {
        let url = fileManager.temporaryDirectory
        return getTemporarySendableFileName(base: base, directoryURL: url)
    }

    @objc public func getTemporarySendableFileName(
        base: String,
        directoryURL: URL,
        pathExtension: String? = nil
    ) -> String {
        let filename = base + "-" + filenameDateFormatter.string(from: Date())

        return getUniqueFilename(from: filename, directoryURL: directoryURL, pathExtension: pathExtension)
    }

    public func getUniqueFilename(
        from filename: String,
        directoryURL: URL,
        pathExtension: String? = nil
    ) -> String {
        var newFilename = filename

        var fileURL = directoryURL.appendingPathComponent(filename)
        if let pathExtension {
            fileURL = fileURL.appendingPathExtension(pathExtension)
        }

        var i = 0
        while fileExists(at: fileURL) {
            newFilename = filename.appending("-\(i)")
            fileURL = directoryURL.appendingPathComponent(newFilename)
            if let pathExtension {
                fileURL = fileURL.appendingPathExtension(pathExtension)
            }
            i += 1
        }

        return newFilename
    }

    // MARK: Directory and file operations

    @objc public func fileExists(at fileURL: URL?) -> Bool {
        guard let fileURL else {
            return false
        }

        return fileManager.fileExists(atPath: fileURL.path)
    }

    public func createEmptyFile(atURL url: URL) throws {
        guard !fileExists(at: url) else {
            throw FileUtilityError.fileAlreadyExists
        }

        try "".write(to: url, atomically: true, encoding: .utf8)
    }

    @objc @discardableResult
    public func createFile(
        atPath path: String,
        contents data: Data?,
        attributes attr: [FileAttributeKey: Any]?
    ) -> Bool {
        fileManager.createFile(atPath: path, contents: data, attributes: attr)
    }

    public func createFileHandle(forReadingFromURL url: URL) throws -> FileHandle {
        try FileHandle(forReadingFrom: url)
    }

    public func createFileHandle(forWritingToURL url: URL) throws -> FileHandle {
        try FileHandle(forWritingTo: url)
    }

    public func closeFileHandle(_ fileHandle: FileHandle) throws {
        try fileHandle.close()
    }

    @objc public func fileExists(atPath path: String) -> Bool {
        fileManager.fileExists(atPath: path)
    }

    public func mkDir(
        at url: URL,
        withIntermediateDirectories createIntermediates: Bool,
        attributes: [FileAttributeKey: Any]?
    ) throws {
        try fileManager.createDirectory(
            at: url,
            withIntermediateDirectories: createIntermediates,
            attributes: attributes
        )
        DDLogInfo("Created directory at \(url.path)")
    }

    /// Create a directory unless there already is one at `url`.
    private func createDirectoryIfNeeded(at url: URL) {
        var isDirectory: ObjCBool = false

        if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) {
            guard !isDirectory.boolValue else {
                return
            }

            DDLogWarn("A file occupies the directory path \(url.path), removing it")
            deleteIfExists(at: url)
        }

        do {
            try mkDir(at: url, withIntermediateDirectories: true, attributes: nil)
        }
        catch {
            DDLogError("Failed to create directory at \(url.path): \(error)")
        }
    }
    
    public func mkDir(
        atPath path: String,
        withIntermediateDirectories createIntermediates: Bool,
        attributes: [FileAttributeKey: Any]?
    ) throws {
        try fileManager.createDirectory(
            atPath: path,
            withIntermediateDirectories: createIntermediates,
            attributes: attributes
        )
    }

    public func dir(at sourceURL: URL?) -> [URL]? {
        guard let sourceURL else {
            return nil
        }

        var items: [URL]?
        if fileManager.fileExists(atPath: sourceURL.path) {
            items = try? fileManager.contentsOfDirectory(at: sourceURL, includingPropertiesForKeys: nil, options: [])
        }
        return items
    }

    public func enumerator(atPath path: String) -> FileManager.DirectoryEnumerator? {
        fileManager.enumerator(atPath: path)
    }

    public func copy(from source: URL, to destination: URL) throws {
        try fileManager.copyItem(at: source, to: destination)
        DDLogInfo("Copied file from: \(source.path) to: \(destination.path)")
    }

    public func move(from sourceURL: URL, to destinationURL: URL) throws {
        try fileManager.moveItem(at: sourceURL, to: destinationURL)
        DDLogInfo("Moved file from: \(sourceURL.path) to: \(destinationURL.path)")
    }
    
    public func move(fromPath srcPath: String, toPath dstPath: String) throws {
        try fileManager.moveItem(atPath: srcPath, toPath: dstPath)
    }

    public func mergeContentsOfPath(from sourceURL: URL, to destinationURL: URL) throws {
        guard fileExists(at: sourceURL) else {
            return
        }

        guard fileExists(at: destinationURL) else {
            try move(from: sourceURL, to: destinationURL)
            return
        }

        let fileEnumerator = enumerator(atPath: sourceURL.path)

        while let fileName = fileEnumerator?.nextObject() as? String {
            let sourceFilePath = sourceURL.appendingPathComponent(fileName)
            let destinationFilePath = destinationURL.appendingPathComponent(fileName)

            if !fileExists(at: destinationFilePath) {
                try move(from: sourceFilePath, to: destinationFilePath)
            }
        }
    }

    public func replaceFile(from sourceURL: URL, to destinationURL: URL) throws {
        guard fileExists(at: sourceURL) else {
            return
        }

        if fileExists(at: destinationURL) {
            try delete(at: destinationURL)
        }
        try move(from: sourceURL, to: destinationURL)
    }

    @objc public func delete(at sourceURL: URL) throws {
        try fileManager.removeItem(atPath: sourceURL.path)
        DDLogInfo("Deleted files or directory at \(sourceURL.path)")
    }

    @objc public func deleteIfExists(at sourceURL: URL?) {
        guard let sourceURL else {
            return
        }

        if fileManager.fileExists(atPath: sourceURL.path) {
            do {
                try delete(at: sourceURL)
            }
            catch {
                DDLogError("Failed to delete file or directory at \(sourceURL.path): \(error)")
            }
        }
    }
    
    public func isDeletableFile(atPath path: String) -> Bool {
        fileManager.isDeletableFile(atPath: path)
    }
    
    @objc public func delete(atPath path: String) throws {
        try fileManager.removeItem(atPath: path)
    }

    public func removeItemsInAllDirectories(appGroupID: String) {
        [
            appDataDirectory(appGroupID: appGroupID),
            appDocumentsDirectory,
            appCachesDirectory,
            appTemporaryDirectory
        ]
        .compactMap(\.self)
        .forEach { removeItemsInDirectory(directoryURL: $0) }

        DDLogNotice("Deleted items in all directories.")
    }

    public func removeItemsInDirectory(directoryURL: URL) {
        if let items = dir(at: directoryURL) {
            items.forEach { deleteIfExists(at: $0) }
        }
    }

    public func cleanTemporaryDirectory(olderThan: Date?) {
        guard let twoDaysAgo = Calendar.current.date(byAdding: .day, value: -2, to: Date()) else {
            DDLogError("Could not get date for two days ago")
            return
        }

        let cutOffDate = olderThan ?? twoDaysAgo

        // The unencrypted directory is a top level entry of `tmp/`, so it is a removal candidate as soon as its own
        // modification date is old enough, and removing it takes everything in it along, however recent. Clean its
        // contents instead and leave the directory in place.
        removeItems(in: appTemporaryDirectory, olderThan: cutOffDate, preserving: [temporaryUnencryptedDirectory])

        if fileExists(at: temporaryUnencryptedDirectory) {
            removeItems(in: temporaryUnencryptedDirectory, olderThan: cutOffDate)
        }
    }

    /// Remove the top level items of a directory that were last modified before a date.
    ///
    /// Subdirectories are removed as a whole, judged by their own modification date. The directory itself is kept.
    /// A failure on one item does not stop the remaining ones from being removed.
    ///
    /// - Parameters:
    ///   - directoryURL: Directory to remove items in
    ///   - date: Items last modified before this date are removed
    ///   - preserved: Items that are never removed, and whose contents are not inspected
    private func removeItems(in directoryURL: URL, olderThan date: Date, preserving preserved: [URL] = []) {
        let preservedPaths = Set(preserved.map(\.path))

        do {
            let items = try fileManager.contentsOfDirectory(
                at: directoryURL,
                includingPropertiesForKeys: [.contentModificationDateKey],
                options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants]
            )

            for item in items where !preservedPaths.contains(item.path) {
                // Keep anything whose age cannot be established
                guard let contentModificationDate = try? item
                    .resourceValues(forKeys: [.contentModificationDateKey])
                    .contentModificationDate,
                    contentModificationDate < date
                else {
                    continue
                }

                do {
                    try fileManager.removeItem(at: item)
                    DDLogInfo("Removed \(item.path)")
                }
                catch {
                    DDLogError("Failed to remove \(item.path): \(error)")
                }
            }
        }
        catch {
            DDLogError("An error occurred while cleaning \(directoryURL.path): \(error)")
        }
    }
    
    public func attributesOfFileSystem(forPath path: String) throws -> [FileAttributeKey: Any] {
        try fileManager.attributesOfFileSystem(forPath: path)
    }
    
    public func contentsOfDirectory(atPath path: String) throws -> [String] {
        try fileManager.contentsOfDirectory(atPath: path)
    }

    public func contentsOfDirectory(
        at url: URL,
        includingPropertiesForKeys keys: [URLResourceKey]?,
        options mask: FileManager.DirectoryEnumerationOptions = []
    ) throws -> [URL] {
        try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: keys, options: mask)
    }

    // MARK: Read and write file

    public func read(fileURL: URL?) -> Data? {
        guard let fileURL else {
            return nil
        }

        var content: Data?

        do {
            let fileHandle = try FileHandle(forReadingFrom: fileURL)
            content = fileHandle.readDataToEndOfFile()
            fileHandle.closeFile()
        }
        catch {
            DDLogError("\(error)")
        }

        return content
    }

    public func read(offset: UInt64?, length: UInt64?, from fileHandle: FileHandle, fileURL: URL) throws -> Data {
        if let offset {
            try fileHandle.seek(toOffset: offset)
        }

        if let length {
            guard let data = try fileHandle.read(upToCount: Int(length)) else {
                throw FileUtilityError.readFailed
            }
            return data
        }
        else {
            guard let data = try fileHandle.readToEnd() else {
                throw FileUtilityError.readFailed
            }
            return data
        }
    }

    public func write(contents: Data, offset: UInt64?, to fileHandle: FileHandle, fileURL: URL) throws {
        if let offset {
            try fileHandle.seek(toOffset: offset)
        }
        else {
            try fileHandle.seekToEnd()
        }
        try fileHandle.write(contentsOf: contents)
    }

    @discardableResult
    public func write(contents: Data?, to fileURL: URL?) -> Bool {
        guard let fileURL else {
            return false
        }

        return fileManager.createFile(atPath: fileURL.path, contents: contents, attributes: nil)
    }

    public func logDirectoriesAndFiles(pathURL: URL) {
        do {
            DDLogNotice("Log files form \(pathURL.path) into debug_log.txt")

            let resourceKeys: [URLResourceKey] = [.creationDateKey, .isDirectoryKey, .fileSizeKey]

            var logFiles = ""

            if let urls = fileManager.enumerator(at: pathURL, includingPropertiesForKeys: resourceKeys) {
                for case let url as URL in urls {
                    let resourceValues = try url.resourceValues(forKeys: Set(resourceKeys))
                    let logFile =
                        "\(url.path) \(ByteCountFormatter.string(fromByteCount: Int64(resourceValues.fileSize ?? 0), countStyle: ByteCountFormatter.CountStyle.file)) \(resourceValues.creationDate!) \(resourceValues.isDirectory!)"
                    DDLogNotice("\(logFile)")
                    logFiles += "\(logFile)\n"
                }
            }
        }
        catch {
            DDLogError("\(error)")
        }
    }

    // MARK: Update file attributes

    public func updateProtectionFormCompleteToCompleteUntilFirstUserAuthentication(at directoryURL: URL) {
        guard let directoryEnumerator = fileManager.enumerator(
            at: directoryURL,
            includingPropertiesForKeys: [.fileProtectionKey],
            options: [],
            errorHandler: { url, error -> Bool in

                DDLogError("Error while enumerating directory \(url.path): \(error)")
                return false
            }
        ) else {
            DDLogError("Could not create directory enumerator at \(directoryURL.path)")
            return
        }

        for file in directoryEnumerator {
            guard let fileURL = file as? URL else {
                continue
            }

            do {
                let resourceValues = try fileURL.resourceValues(forKeys: [.fileProtectionKey])
                guard resourceValues.fileProtection == .complete else {
                    continue
                }

                try (fileURL as NSURL).setResourceValue(
                    URLFileProtection.completeUntilFirstUserAuthentication,
                    forKey: URLResourceKey.fileProtectionKey
                )
            }
            catch {
                DDLogError("Failed to set protection level for file \(fileURL.path): \(error)")
            }
        }
    }

    public func backup(of namePrefixes: [String], exclude: Bool, appGroupID: String) throws {
        func setIsExcludedFromBackup(_ sourceURL: URL?, namePrefixes: [String]?, exclude: Bool) {
            guard let sourceURL else {
                return
            }
            
            guard let resources = dir(at: sourceURL) else {
                return
            }

            for resource in resources {
                if let namePrefixes {
                    guard namePrefixes.contains(where: { name in
                        resource.lastPathComponent.hasPrefix(name)
                    }) else {
                        continue
                    }
                }

                do {
                    try resource.setResourceValue(exclude, forKey: .isExcludedFromBackupKey)
                    DDLogInfo("Excluded from backup attribute set for \(sourceURL.path)")
                }
                catch {
                    DDLogError("Failed to set is excluded from backup attribute for \(sourceURL.path): \(error)")
                }
            }
        }

        setIsExcludedFromBackup(
            appDocumentsDirectory,
            namePrefixes: namePrefixes,
            exclude: exclude
        )

        setIsExcludedFromBackup(
            appDataDirectory(appGroupID: appGroupID),
            namePrefixes: namePrefixes,
            exclude: exclude
        )

        setIsExcludedFromBackup(
            appCachesDirectory,
            namePrefixes: nil,
            exclude: exclude
        )
    }
}

extension URL {
    func setResourceValue(_ value: Any, forKey key: URLResourceKey) throws {
        try (self as NSURL).setResourceValue(value, forKey: key)
    }

    var isDirectory: Bool {
        (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
    }
}

Messung
CHZG
829610092

¤ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.