@@ -16,10 +16,12 @@ package recipes
1616
1717import (
1818 "encoding/json"
19+ "io"
1920 "io/ioutil"
2021 "os"
2122 "path/filepath"
2223 "runtime"
24+ "sort"
2325 "time"
2426)
2527
@@ -29,76 +31,108 @@ var (
2931 dbFileName = "osconfig_recipedb"
3032)
3133
34+ type timeFunc func () time.Time
35+
3236// RecipeDB represents local state of installed recipes.
33- type RecipeDB map [string ]Recipe
37+ type recipeDB struct {
38+ file string
39+ timeFunc timeFunc
3440
35- // newRecipeDB instantiates a recipeDB.
36- func newRecipeDB () (RecipeDB , error ) {
37- db := make (RecipeDB )
38- f , err := os .Open (filepath .Join (getDbDir (), dbFileName ))
41+ recipes map [string ]Recipe
42+ }
43+
44+ func newRecipeDB (path string ) (* recipeDB , error ) {
45+ db := & recipeDB {
46+ file : path ,
47+ timeFunc : time .Now ,
48+ recipes : make (map [string ]Recipe , 0 ),
49+ }
50+
51+ f , err := os .Open (path )
3952 if err != nil {
4053 if os .IsNotExist (err ) {
4154 return db , nil
4255 }
56+
4357 return nil , err
4458 }
4559 defer f .Close ()
46- bytes , err := ioutil .ReadAll (f )
60+
61+ raw , err := io .ReadAll (f )
4762 if err != nil {
4863 return nil , err
4964 }
50- var recipelist []Recipe
51- if err := json .Unmarshal (bytes , & recipelist ); err != nil {
65+
66+ var recipes []Recipe
67+ if err := json .Unmarshal (raw , & recipes ); err != nil {
5268 return nil , err
5369 }
54- for _ , recipe := range recipelist {
55- db [recipe .Name ] = recipe
70+
71+ for _ , recipe := range recipes {
72+ db .recipes [recipe .Name ] = recipe
5673 }
5774 return db , nil
5875}
5976
77+ // newRecipeDB instantiates a recipeDB.
78+ func newRecipeDBWithDefaults () (* recipeDB , error ) {
79+ dir , fileName := getDbDir (), dbFileName
80+ return newRecipeDB (filepath .Join (dir , fileName ))
81+ }
82+
6083// getRecipe returns the Recipe object for the given recipe name.
61- func (db RecipeDB ) getRecipe (name string ) (Recipe , bool ) {
62- r , ok := db [name ]
84+ func (db * recipeDB ) getRecipe (name string ) (Recipe , bool ) {
85+ r , ok := db . recipes [name ]
6386 return r , ok
6487}
6588
6689// addRecipe marks a recipe as installed.
67- func (db RecipeDB ) addRecipe (name , version string , success bool ) error {
90+ func (db * recipeDB ) addRecipe (name , version string , success bool ) error {
6891 versionNum , err := convertVersion (version )
6992 if err != nil {
7093 return err
7194 }
72- db [name ] = Recipe {Name : name , Version : versionNum , InstallTime : time . Now ().Unix (), Success : success }
95+ db . recipes [name ] = Recipe {Name : name , Version : versionNum , InstallTime : db . timeFunc ().Unix (), Success : success }
7396
74- var recipelist []Recipe
75- for _ , recipe := range db {
76- recipelist = append (recipelist , recipe )
97+ return db .saveToFS ()
98+ }
99+
100+ func (db * recipeDB ) saveToFS () error {
101+ var recipes []Recipe
102+ for _ , recipe := range db .recipes {
103+ recipes = append (recipes , recipe )
77104 }
78- dbBytes , err := json .Marshal (recipelist )
105+
106+ sort .Slice (recipes , func (i , j int ) bool {
107+ return recipes [i ].Name < recipes [j ].Name
108+ })
109+
110+ raw , err := json .Marshal (recipes )
79111 if err != nil {
80112 return err
81113 }
82114
83- dbDir := getDbDir ( )
84- if err := os .MkdirAll (dbDir , 0755 ); err != nil {
115+ dir := filepath . Dir ( db . file )
116+ if err := os .MkdirAll (dir , 0755 ); err != nil {
85117 return err
86118 }
87119
88- f , err := ioutil .TempFile (dbDir , dbFileName + "_*" )
120+ fileName := filepath .Base (db .file )
121+ f , err := ioutil .TempFile (dir , fileName + "_*" )
89122 if err != nil {
90123 return err
91124 }
92125
93- if _ , err := f .Write (dbBytes ); err != nil {
126+ if _ , err := f .Write (raw ); err != nil {
94127 f .Close ()
95128 return err
96129 }
130+
97131 if err := f .Close (); err != nil {
98132 return err
99133 }
100134
101- return os .Rename (f .Name (), filepath . Join ( dbDir , dbFileName ) )
135+ return os .Rename (f .Name (), db . file )
102136}
103137
104138func getDbDir () string {
0 commit comments