]> ruderich.org/simon Gitweb - nsscash/nsscash.git/blob - state.go
nss: Makefile: don't link against asan
[nsscash/nsscash.git] / state.go
1 // Read and write the state file used to keep data over multiple runs
2
3 // Copyright (C) 2019  Simon Ruderich
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU Affero General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU Affero General Public License for more details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18 package main
19
20 import (
21         "encoding/json"
22         "io/ioutil"
23         "os"
24         "path/filepath"
25         "reflect"
26         "time"
27 )
28
29 type State struct {
30         LastModified map[string]time.Time
31
32         // Copy of LastModified to write the state file only on changes
33         origLastModified map[string]time.Time
34 }
35
36 func LoadState(path string) (*State, error) {
37         var state State
38
39         x, err := ioutil.ReadFile(path)
40         if err != nil {
41                 // It's fine if the state file does not exist yet, we'll
42                 // create it later when writing the state
43                 if !os.IsNotExist(err) {
44                         return nil, err
45                 }
46         } else {
47                 err := json.Unmarshal(x, &state)
48                 if err != nil {
49                         return nil, err
50                 }
51         }
52
53         if state.LastModified == nil {
54                 state.LastModified = make(map[string]time.Time)
55         }
56
57         state.origLastModified = make(map[string]time.Time)
58         for k, v := range state.LastModified {
59                 state.origLastModified[k] = v
60         }
61
62         return &state, nil
63 }
64
65 func WriteStateIfChanged(path string, state *State) error {
66         // State hasn't changed, nothing to do
67         if reflect.DeepEqual(state.LastModified, state.origLastModified) {
68                 return nil
69         }
70
71         x, err := json.Marshal(state)
72         if err != nil {
73                 return err
74         }
75
76         // Write the file in an atomic fashion by creating a temporary file
77         // and renaming it over the target file
78
79         dir := filepath.Dir(path)
80         name := filepath.Base(path)
81
82         f, err := ioutil.TempFile(dir, "tmp-"+name+"-")
83         if err != nil {
84                 return err
85         }
86         defer os.Remove(f.Name())
87         defer f.Close()
88
89         _, err = f.Write(x)
90         if err != nil {
91                 return err
92         }
93         err = f.Sync()
94         if err != nil {
95                 return err
96         }
97         return os.Rename(f.Name(), path)
98 }