]> ruderich.org/simon Gitweb - nsscash/nsscash.git/blob - state.go
Guarantee durability after renaming temporary files
[nsscash/nsscash.git] / state.go
1 // Read and write the state file used to keep data over multiple runs
2
3 // Copyright (C) 2019-2021  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         "time"
26
27         "github.com/google/renameio"
28 )
29
30 type State struct {
31         // Key is File.Url
32         LastModified map[string]time.Time
33         Checksum     map[string]string // SHA512 in hex
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         if state.Checksum == nil {
57                 state.Checksum = make(map[string]string)
58         }
59
60         return &state, nil
61 }
62
63 func WriteState(path string, state *State) error {
64         // Update the state file even if nothing has changed to provide a
65         // simple way to check if nsscash ran successfully (the state is only
66         // updated if there were no errors)
67
68         x, err := json.Marshal(state)
69         if err != nil {
70                 return err
71         }
72
73         f, err := renameio.TempFile(filepath.Dir(path), path)
74         if err != nil {
75                 return err
76         }
77         defer f.Cleanup()
78
79         _, err = f.Write(x)
80         if err != nil {
81                 return err
82         }
83         err = f.CloseAtomicallyReplace()
84         if err != nil {
85                 return err
86         }
87         return syncPath(filepath.Dir(path))
88 }