Spaces:
Sleeping
Sleeping
| import Foundation | |
| extension FileHandle { | |
| /// Check if the file was deleted on the file system. Linux keep the file alive, as long as some processes have it open. | |
| public func wasDeleted() -> Bool { | |
| // This field contains the number of hard links to the file. | |
| return fileStats().st_nlink == 0 | |
| } | |
| public func fileSize() -> Int { | |
| return Int(fileStats().st_size) | |
| } | |
| public func fileSizeAndModificationTime() -> (size: Int, modificationTime: Date, creationTime: Date) { | |
| let stats = fileStats() | |
| return (Int(stats.st_size), stats.modificationTime, stats.creationTime) | |
| } | |
| /// Return file `stat` structure | |
| public func fileStats() -> stat { | |
| var stats = stat() | |
| guard fstat(fileDescriptor, &stats) != -1 else { | |
| let error = String(cString: strerror(errno)) | |
| fatalError("fstat failed on open file descriptor. Error \(errno) \(error)") | |
| } | |
| return stats | |
| } | |
| } | |
| public enum FileHandleError: Error { | |
| case cannotMoveFile(from: String, to: String, errno: Int32, error: String) | |
| } | |
| extension FileManager { | |
| /// Rename file and replace if `to` already exists. https://www.gnu.org/software/libc/manual/html_node/Renaming-Files.html | |
| public func moveFileOverwrite(from: String, to: String) throws { | |
| guard rename(from, to) != -1 else { | |
| let error = String(cString: strerror(errno)) | |
| throw FileHandleError.cannotMoveFile(from: from, to: to, errno: errno, error: error) | |
| } | |
| } | |
| public func removeItemIfExists(at: String) throws { | |
| if fileExists(atPath: at) { | |
| try removeItem(atPath: at) | |
| } | |
| } | |
| /// Return file `stat` structure | |
| public func fileStats(at: String) -> stat? { | |
| var stats = stat() | |
| let ret = stat(at, &stats) | |
| guard ret != -1 else { | |
| if errno == 2 { | |
| // No such file or directory | |
| return nil | |
| } | |
| let error = String(cString: strerror(errno)) | |
| fatalError("fstat failed on open file descriptor. Error \(errno) \(error), ret=\(ret)") | |
| } | |
| return stats | |
| } | |
| /// Get modification and creation time | |
| public func fileSizeAndModificationTime(at: String) -> (size: Int, modificationTime: Date, creationTime: Date)? { | |
| guard let stats = fileStats(at: at) else { | |
| return nil | |
| } | |
| return (Int(stats.st_size), stats.modificationTime, stats.creationTime) | |
| } | |
| /// Wait until the file was not updated for at least 60 seconds. If the file does not exist, do nothing | |
| public func waitIfFileWasRecentlyModified(at: String, waitTimeMinutes: Int = 15) { | |
| // Wait up to 15 minutes | |
| for _ in 0 ..< (waitTimeMinutes*6) { | |
| guard let mTime = FileManager.default.fileStats(at: at)?.modificationTime, | |
| mTime > Date().addingTimeInterval(-60) else { | |
| break | |
| } | |
| print("Another process is writing to \(at). Check in 10s. Waiting up to \(waitTimeMinutes) minutes.") | |
| sleep(10) | |
| } | |
| } | |
| } | |
| extension stat { | |
| /// Last modification time of the file | |
| public var modificationTime: Date { | |
| #if os(Linux) | |
| let seconds = Double(st_mtim.tv_sec) | |
| let nanosends = Double(st_mtim.tv_nsec) | |
| #else | |
| let seconds = Double(st_mtimespec.tv_sec) | |
| let nanosends = Double(st_mtimespec.tv_nsec) | |
| #endif | |
| return Date(timeIntervalSince1970: seconds + nanosends / 1_000_000) | |
| } | |
| /// Creation time of the file / inode | |
| public var creationTime: Date { | |
| #if os(Linux) | |
| let seconds = Double(st_ctim.tv_sec) | |
| let nanosends = Double(st_ctim.tv_nsec) | |
| #else | |
| let seconds = Double(st_ctimespec.tv_sec) | |
| let nanosends = Double(st_ctimespec.tv_nsec) | |
| #endif | |
| return Date(timeIntervalSince1970: seconds + nanosends / 1_000_000) | |
| } | |
| } | |