github.com/omniscale/go-osm@v0.3.1/state/state.go (about) 1 package state 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "strconv" 10 "strings" 11 "time" 12 ) 13 14 type DiffState struct { 15 Time time.Time 16 Sequence int 17 URL string 18 } 19 20 func (d DiffState) write(w io.Writer) error { 21 lines := []string{} 22 lines = append(lines, "timestamp="+d.Time.Format(timestampFormat)) 23 if d.Sequence != 0 { 24 lines = append(lines, "sequenceNumber="+fmt.Sprintf("%d", d.Sequence)) 25 } 26 lines = append(lines, "replicationUrl="+d.URL) 27 28 for _, line := range lines { 29 _, err := w.Write([]byte(line + "\n")) 30 if err != nil { 31 return err 32 } 33 } 34 return nil 35 } 36 37 func WriteFile(filename string, state *DiffState) error { 38 tmpname := filename + "~" 39 f, err := os.Create(tmpname) 40 if err != nil { 41 return fmt.Errorf("creating temp file for writing state file: %w", err) 42 } 43 err = state.write(f) 44 if err != nil { 45 f.Close() 46 os.Remove(tmpname) 47 return fmt.Errorf("writing state to %q: %w", tmpname, err) 48 } 49 f.Close() 50 return os.Rename(tmpname, filename) 51 } 52 53 func ParseFile(stateFile string) (*DiffState, error) { 54 f, err := os.Open(stateFile) 55 if err != nil { 56 return nil, err 57 } 58 defer f.Close() 59 return Parse(f) 60 } 61 62 // Parse parses an INI style state.txt file. 63 // timestamp is required, sequenceNumber and replicationUrl can be empty. 64 func Parse(f io.Reader) (*DiffState, error) { 65 values, err := parseSimpleIni(f) 66 if err != nil { 67 return nil, fmt.Errorf("parsing state file as INI: %w", err) 68 } 69 70 timestamp, err := parseTimeStamp(values["timestamp"]) 71 if err != nil { 72 return nil, err 73 } 74 sequence, err := parseSequence(values["sequenceNumber"]) 75 if err != nil { 76 return nil, err 77 } 78 79 url := values["replicationUrl"] 80 return &DiffState{ 81 Time: timestamp, 82 Sequence: sequence, 83 URL: url, 84 }, nil 85 } 86 87 func parseSimpleIni(f io.Reader) (map[string]string, error) { 88 result := make(map[string]string) 89 90 reader := bufio.NewScanner(f) 91 for reader.Scan() { 92 line := reader.Text() 93 if line != "" && line[0] == '#' { 94 continue 95 } 96 if strings.Contains(line, "=") { 97 keyVal := strings.SplitN(line, "=", 2) 98 result[strings.TrimSpace(keyVal[0])] = strings.TrimSpace(keyVal[1]) 99 } 100 101 } 102 if err := reader.Err(); err != nil { 103 return nil, err 104 } 105 106 return result, nil 107 } 108 109 const timestampFormat = "2006-01-02T15\\:04\\:05Z" 110 111 func parseTimeStamp(value string) (time.Time, error) { 112 if value == "" { 113 return time.Time{}, errors.New("missing timestamp in state") 114 } 115 return time.Parse(timestampFormat, value) 116 } 117 118 func parseSequence(value string) (int, error) { 119 if value == "" { 120 return 0, nil 121 } 122 val, err := strconv.ParseInt(value, 10, 32) 123 return int(val), err 124 }