Skip to content

Commit

Permalink
feat: add sync command
Browse files Browse the repository at this point in the history
  • Loading branch information
ledif committed Feb 25, 2025
1 parent 567e0d5 commit 6e73b3f
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 35 deletions.
84 changes: 50 additions & 34 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,61 @@ import (
"os"
)


var applyCmd = &cobra.Command{
Use: "apply [path]",
Short: "Apply changes from a profile.json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := applyProfile(args[0])
if err != nil {
fmt.Printf("Error: %s\n", err)
}
},
}

var revertCmd = &cobra.Command{
Use: "revert [path]",
Short: "Revert changes from a profile.json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := revertProfile(args[0])
if err != nil {
fmt.Printf("Error: %s\n", err)
}
},
}

var recordCmd = &cobra.Command{
Use: "record",
Short: "Record changes to xsettings and dump them as a profile on SIGINT",
Run: func(cmd *cobra.Command, args []string) {
recordProfile()
},
}

var syncCmd = &cobra.Command{
Use: "sync",
Short: "Sync user profile with distribution's recommended profile",
Run: func(cmd *cobra.Command, args []string) {
distProfile, _ := cmd.Flags().GetString("profile")
if err := syncProfile(distProfile); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
},
}

func init() {
syncCmd.Flags().StringP("profile", "p", "/usr/share/xfconf-profile/default.json", "Path to the distribution's recommended profile")
}

func main() {
var rootCmd = &cobra.Command{
Use: "xfconf-profile",
Short: "Tool for applying, reverting and managing Xfce profiles",
}

var applyCmd = &cobra.Command{
Use: "apply [path]",
Short: "Apply changes from a profile.json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := applyProfile(args[0])
if err != nil {
fmt.Printf("Error: %s\n", err)
}
},
}

var revertCmd = &cobra.Command{
Use: "revert [path]",
Short: "Revert changes from a profile.json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := revertProfile(args[0])
if err != nil {
fmt.Printf("Error: %s\n", err)
}
},
}

var recordCmd = &cobra.Command{
Use: "record",
Short: "Record changes to xsettings and dump them as a profile on SIGINT",
Run: func(cmd *cobra.Command, args []string) {
recordProfile()
},
}

rootCmd.AddCommand(applyCmd, revertCmd, recordCmd)
rootCmd.AddCommand(applyCmd, revertCmd, recordCmd, syncCmd)

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
Expand Down
136 changes: 135 additions & 1 deletion profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package main

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/fatih/color"
)
Expand All @@ -30,7 +33,7 @@ func applyProfile(profilePath string) error {
}
for property, value := range properties {
fmt.Printf("%s Setting %s::%s ➔ %s\n", blue("•"), channel, property, value)
cmd := exec.Command("xfconf-query", "-c", channel, "--property", property, "--set", fmt.Sprintf("%v", value))
cmd := exec.Command("xfconf-query", "-c", channel, "--property", property, "--type", "string", "--create", "--set", fmt.Sprintf("%v", value))

output, err := cmd.CombinedOutput()
if err != nil {
Expand Down Expand Up @@ -75,3 +78,134 @@ func revertProfile(profilePath string) error {

return nil
}


// Create $XDG_STATE_HOME/xfconf-profile/sync if needed
func ensureStateDir() (string, error) {
xdgStateHome := os.Getenv("XDG_STATE_HOME")
var stateDirPath string

if xdgStateHome != "" {
stateDirPath = filepath.Join(xdgStateHome, "xfconf-profile", "sync")
} else {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get user home directory: %v", err)
}
stateDirPath = filepath.Join(homeDir, ".local", "state", "xfconf-profile", "sync")
}

if err := os.MkdirAll(stateDirPath, 0755); err != nil {
return "", fmt.Errorf("failed to create state directory: %v", err)
}

return stateDirPath, nil
}

func copyDistConfig(distConfig string, currentDir string) error {
defaultConfigData, err := ioutil.ReadFile(distConfig)
if err != nil {
return fmt.Errorf("failed to read config: %v", err)
}

currentConfigPath := filepath.Join(currentDir, "profile.json")
if err := ioutil.WriteFile(currentConfigPath, defaultConfigData, 0644); err != nil {
return fmt.Errorf("failed to write current config: %v", err)
}

return nil
}

func compareFiles(file1, file2 string) (bool, error) {
data1, err := ioutil.ReadFile(file1)
if err != nil {
return false, fmt.Errorf("failed to read %s: %v", file1, err)
}

data2, err := ioutil.ReadFile(file2)
if err != nil {
return false, fmt.Errorf("failed to read %s: %v", file2, err)
}

return string(data1) == string(data2), nil
}

func syncProfile(distConfig string) error {
stateDirPath, err := ensureStateDir()
if err != nil {
return err
}

_, err = os.Stat(distConfig)
if err != nil {
return err
}

currentDir := filepath.Join(stateDirPath, "current")
previousDir := filepath.Join(stateDirPath, "previous")

// Abnormal case: reset state directory if it's invalid
if _, err := os.Stat(currentDir); errors.Is(err, os.ErrNotExist) {
if _, err := os.Stat(previousDir); err == nil {
fmt.Println("Invalid state: resetting data")
if err := os.RemoveAll(stateDirPath); err != nil {
return fmt.Errorf("failed to reset state directory: %v", err)
}
if _, err := ensureStateDir(); err != nil {
return err
}
}
}

// First run: initialize current directory
if _, err := os.Stat(currentDir); errors.Is(err, os.ErrNotExist) {
fmt.Println("Empty state")
if err := applyProfile(distConfig); err != nil {
return err
}
if err := os.MkdirAll(currentDir, 0755); err != nil {
return fmt.Errorf("failed to create current directory: %v", err)
}
if err := copyDistConfig(distConfig, currentDir); err != nil {
return err
}
return nil
}

// Steady run: move current to previous and apply new config
fmt.Println("Steady state")
if err := os.RemoveAll(previousDir); err != nil {
return fmt.Errorf("failed to remove previous directory: %v", err)
}
if err := os.Rename(currentDir, previousDir); err != nil {
return fmt.Errorf("failed to move current to previous: %v", err)
}
if err := os.MkdirAll(currentDir, 0755); err != nil {
return fmt.Errorf("failed to create current directory: %v", err)
}
if err := copyDistConfig(distConfig, currentDir); err != nil {
return err
}

// Check if configurations differ
currentConfig := filepath.Join(currentDir, "profile.json")
previousConfig := filepath.Join(previousDir, "profile.json")
identical, err := compareFiles(currentConfig, previousConfig)
if err != nil {
return err
}

if !identical {
fmt.Println("Configurations differ -- reverting old and applying new")
if err := revertProfile(previousConfig); err != nil {
return err
}
if err := applyProfile(currentConfig); err != nil {
return err
}
} else {
fmt.Println("Configurations identical -- no changes required")
}

return nil
}

0 comments on commit 6e73b3f

Please sign in to comment.