@@ -2,9 +2,12 @@ package main
22
33import (
44 "encoding/json"
5+ "errors"
56 "fmt"
67 "io/ioutil"
8+ "os"
79 "os/exec"
10+ "path/filepath"
811 "strings"
912 "github.com/fatih/color"
1013)
@@ -30,7 +33,7 @@ func applyProfile(profilePath string) error {
3033 }
3134 for property , value := range properties {
3235 fmt .Printf ("%s Setting %s::%s ➔ %s\n " , blue ("•" ), channel , property , value )
33- cmd := exec .Command ("xfconf-query" , "-c" , channel , "--property" , property , "--set" , fmt .Sprintf ("%v" , value ))
36+ cmd := exec .Command ("xfconf-query" , "-c" , channel , "--property" , property , "--type" , "string" , "--create" , "-- set" , fmt .Sprintf ("%v" , value ))
3437
3538 output , err := cmd .CombinedOutput ()
3639 if err != nil {
@@ -75,3 +78,134 @@ func revertProfile(profilePath string) error {
7578
7679 return nil
7780}
81+
82+
83+ // Create $XDG_STATE_HOME/xfconf-profile/sync if needed
84+ func ensureStateDir () (string , error ) {
85+ xdgStateHome := os .Getenv ("XDG_STATE_HOME" )
86+ var stateDirPath string
87+
88+ if xdgStateHome != "" {
89+ stateDirPath = filepath .Join (xdgStateHome , "xfconf-profile" , "sync" )
90+ } else {
91+ homeDir , err := os .UserHomeDir ()
92+ if err != nil {
93+ return "" , fmt .Errorf ("failed to get user home directory: %v" , err )
94+ }
95+ stateDirPath = filepath .Join (homeDir , ".local" , "state" , "xfconf-profile" , "sync" )
96+ }
97+
98+ if err := os .MkdirAll (stateDirPath , 0755 ); err != nil {
99+ return "" , fmt .Errorf ("failed to create state directory: %v" , err )
100+ }
101+
102+ return stateDirPath , nil
103+ }
104+
105+ func copyDistConfig (distConfig string , currentDir string ) error {
106+ defaultConfigData , err := ioutil .ReadFile (distConfig )
107+ if err != nil {
108+ return fmt .Errorf ("failed to read config: %v" , err )
109+ }
110+
111+ currentConfigPath := filepath .Join (currentDir , "profile.json" )
112+ if err := ioutil .WriteFile (currentConfigPath , defaultConfigData , 0644 ); err != nil {
113+ return fmt .Errorf ("failed to write current config: %v" , err )
114+ }
115+
116+ return nil
117+ }
118+
119+ func compareFiles (file1 , file2 string ) (bool , error ) {
120+ data1 , err := ioutil .ReadFile (file1 )
121+ if err != nil {
122+ return false , fmt .Errorf ("failed to read %s: %v" , file1 , err )
123+ }
124+
125+ data2 , err := ioutil .ReadFile (file2 )
126+ if err != nil {
127+ return false , fmt .Errorf ("failed to read %s: %v" , file2 , err )
128+ }
129+
130+ return string (data1 ) == string (data2 ), nil
131+ }
132+
133+ func syncProfile (distConfig string ) error {
134+ stateDirPath , err := ensureStateDir ()
135+ if err != nil {
136+ return err
137+ }
138+
139+ _ , err = os .Stat (distConfig )
140+ if err != nil {
141+ return err
142+ }
143+
144+ currentDir := filepath .Join (stateDirPath , "current" )
145+ previousDir := filepath .Join (stateDirPath , "previous" )
146+
147+ // Abnormal case: reset state directory if it's invalid
148+ if _ , err := os .Stat (currentDir ); errors .Is (err , os .ErrNotExist ) {
149+ if _ , err := os .Stat (previousDir ); err == nil {
150+ fmt .Println ("Invalid state: resetting data" )
151+ if err := os .RemoveAll (stateDirPath ); err != nil {
152+ return fmt .Errorf ("failed to reset state directory: %v" , err )
153+ }
154+ if _ , err := ensureStateDir (); err != nil {
155+ return err
156+ }
157+ }
158+ }
159+
160+ // First run: initialize current directory
161+ if _ , err := os .Stat (currentDir ); errors .Is (err , os .ErrNotExist ) {
162+ fmt .Println ("Empty state" )
163+ if err := applyProfile (distConfig ); err != nil {
164+ return err
165+ }
166+ if err := os .MkdirAll (currentDir , 0755 ); err != nil {
167+ return fmt .Errorf ("failed to create current directory: %v" , err )
168+ }
169+ if err := copyDistConfig (distConfig , currentDir ); err != nil {
170+ return err
171+ }
172+ return nil
173+ }
174+
175+ // Steady run: move current to previous and apply new config
176+ fmt .Println ("Steady state" )
177+ if err := os .RemoveAll (previousDir ); err != nil {
178+ return fmt .Errorf ("failed to remove previous directory: %v" , err )
179+ }
180+ if err := os .Rename (currentDir , previousDir ); err != nil {
181+ return fmt .Errorf ("failed to move current to previous: %v" , err )
182+ }
183+ if err := os .MkdirAll (currentDir , 0755 ); err != nil {
184+ return fmt .Errorf ("failed to create current directory: %v" , err )
185+ }
186+ if err := copyDistConfig (distConfig , currentDir ); err != nil {
187+ return err
188+ }
189+
190+ // Check if configurations differ
191+ currentConfig := filepath .Join (currentDir , "profile.json" )
192+ previousConfig := filepath .Join (previousDir , "profile.json" )
193+ identical , err := compareFiles (currentConfig , previousConfig )
194+ if err != nil {
195+ return err
196+ }
197+
198+ if ! identical {
199+ fmt .Println ("Configurations differ -- reverting old and applying new" )
200+ if err := revertProfile (previousConfig ); err != nil {
201+ return err
202+ }
203+ if err := applyProfile (currentConfig ); err != nil {
204+ return err
205+ }
206+ } else {
207+ fmt .Println ("Configurations identical -- no changes required" )
208+ }
209+
210+ return nil
211+ }
0 commit comments