File size: 5,055 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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import Foundation
import OmFileFormat

enum ModelTimeVariable: String, GenericVariable {
    case initialisation_time
    case modification_time
    
    var omFileName: (file: String, level: Int) {
        return (rawValue, 0)
    }
    
    var scalefactor: Float {
        return 1
    }
    
    var interpolation: ReaderInterpolation {
        return .linear
    }
    
    var unit: SiUnit {
        return .seconds
    }
    
    var isElevationCorrectable: Bool {
        return false
    }
    
    var storePreviousForecast: Bool {
        return true
    }
    
    var requiresOffsetCorrectionForMixing: Bool {
        return false
    }
}


/**
 TODO:
 - CAMS, IconWave, GloFas, seasonal forecast, CMIP, satellite
 - run end lenght might be too short for side-runs
 - license
 - name of provider
 - spatial resolution
 - area / region
 - grid system / proj string?
 - list of variables? pressure levels, model levels?
 - forecast length (per run?)
 - model forecast steps with 1,3,6 hour switching?
 */
struct ModelUpdateMetaJson: Codable {
    /// Model initilsiation time as unix timestamp. E.g. 0z
    let last_run_initialisation_time: Int
    
    /// Last modification time. The time the conversion finished on the download and processing server
    let last_run_modification_time: Int
    
    /// Time at which that model run has been available on the current server
    let last_run_availability_time: Int
    
    /// Data temporal resolution in seconds. E.g. 3600 for 1-hourly data
    let temporal_resolution_seconds: Int
    
    /// First date of available data -> Also different per server / variable etc
    //let data_start_time: Int
    
    /// End of updated timerange. The last timestep is not included! -> Probably not reliable at all.... Short runs, upper model runs, etc....
    let data_end_time: Int
    
    /// E.g. `3600` for updates every 1 hour
    let update_interval_seconds: Int
    
    /// Time at which that model run has been available on the current server
    var lastRunAvailabilityTime: Timestamp {
        Timestamp(last_run_availability_time)
    }
    
    /// Write a new meta data JSON
    static func update(domain: GenericDomain, run: Timestamp, end: Timestamp, now: Timestamp = .now()) throws {
        let meta = ModelUpdateMetaJson(
            last_run_initialisation_time: run.timeIntervalSince1970,
            last_run_modification_time: now.timeIntervalSince1970,
            last_run_availability_time: now.timeIntervalSince1970,
            temporal_resolution_seconds: domain.dtSeconds,
            //data_start_time: 0,
            data_end_time: end.timeIntervalSince1970,
            update_interval_seconds: domain.updateIntervalSeconds
        )
        let path = OmFileManagerReadable.meta(domain: domain.domainRegistry)
        try path.createDirectory()
        let pathString = path.getFilePath()
        try meta.writeTo(path: pathString)
    }
    
    /// Update the availability time and return a new object
    func with(last_run_availability_time: Timestamp) -> ModelUpdateMetaJson {
        return ModelUpdateMetaJson(
            last_run_initialisation_time: last_run_initialisation_time,
            last_run_modification_time: last_run_modification_time,
            last_run_availability_time: last_run_availability_time.timeIntervalSince1970,
            temporal_resolution_seconds: temporal_resolution_seconds,
            data_end_time: data_end_time,
            update_interval_seconds: update_interval_seconds
        )
    }
}

extension Encodable {
    /// Write to as an atomic operation
    func writeTo(path: String) throws {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .sortedKeys
        let fn = try FileHandle.createNewFile(file: "\(path)~")
        try fn.write(contentsOf: try encoder.encode(self))
        try fn.close()
        try FileManager.default.moveFileOverwrite(from: "\(path)~", to: path)
    }
}

/// Intermediate structure to keep meta files open
struct ModelUpdateMetaJsonAndFileHandle: GenericFileManagable {
    let fn: FileHandle
    let meta: ModelUpdateMetaJson
    
    func wasDeleted() -> Bool {
        fn.wasDeleted()
    }
    
    static func open(from: OmFileManagerReadable) throws -> ModelUpdateMetaJsonAndFileHandle? {
        guard let fn = try? FileHandle.openFileReading(file: from.getFilePath()) else {
            return nil
        }
        guard let data = try fn.readToEnd() else {
            return nil
        }
        guard let json = try? JSONDecoder().decode(ModelUpdateMetaJson.self, from: data) else {
            return nil
        }
        return .init(fn: fn, meta: json)
    }
}

/// Cache access to metadata JSONs
struct MetaFileManager {
    public static var instance = GenericFileManager<ModelUpdateMetaJsonAndFileHandle>()
    
    private init() {}
    
    /// Get cached file or return nil, if the files does not exist
    public static func get(_ file: OmFileManagerReadable) throws -> ModelUpdateMetaJson? {
        try instance.get(file)?.meta
    }
}