File size: 4,019 Bytes
6ee917b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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)
    }
}