open-wether / Sources /App /Helper /FileHandleExtensions.swift
soiz1's picture
Migrated from GitHub
6ee917b verified
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)
}
}