Skip to content

Commit

Permalink
feat(core): enhancements to platform lib interface and implementers
Browse files Browse the repository at this point in the history
  • Loading branch information
jakedoublev committed Jan 21, 2025
1 parent cb01d30 commit 1b18bde
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 44 deletions.
28 changes: 21 additions & 7 deletions pkg/platform/darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,31 @@ func (p PlatformDarwin) GetUsername() string {
return p.username
}

// GetUserHomeDir returns the user's home directory on macOS.
func (p PlatformDarwin) GetUserHomeDir() string {
// UserHomeDir returns the user's home directory on macOS.
func (p PlatformDarwin) UserHomeDir() string {
return p.userHomeDir
}

// GetDataDirectory returns the data directory for macOS.
func (p PlatformDarwin) GetDataDirectory() string {
// UserAppDataDirectory returns the namespaced user-level data directory for macOS.
// i.e. ~/Library/Application Support/<serviceNamespace>
func (p PlatformDarwin) UserAppDataDirectory() string {
return filepath.Join(p.userHomeDir, "Library", "Application Support", p.serviceNamespace)
}

// GetConfigDirectory returns the config directory for macOS.
func (p PlatformDarwin) GetConfigDirectory() string {
return filepath.Join(p.userHomeDir, "Library", "Preferences", p.serviceNamespace)
// UserAppConfigDirectory returns the namespaced user-level config directory for macOS.
// i.e. ~/Library/Application Support/<serviceNamespace>
func (p PlatformDarwin) UserAppConfigDirectory() string {
return filepath.Join(p.userHomeDir, "Library", "Application Support", p.serviceNamespace)
}

// SystemAppDataDirectory returns the namespaced system-level data directory for macOS.
// i.e. /Library/Application Support/<serviceNamespace>
func (p PlatformDarwin) SystemAppDataDirectory() string {
return filepath.Join("/", "Library", "Application Support", p.serviceNamespace)
}

// SystemAppConfigDirectory returns the namespaced system-level config directory for macOS.
// i.e. /Library/Application Support/<serviceNamespace>
func (p PlatformDarwin) SystemAppConfigDirectory() string {
return filepath.Join("/", "Library", "Application Support", p.serviceNamespace)
}
28 changes: 20 additions & 8 deletions pkg/platform/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,36 @@ func NewPlatformLinux(serviceNamespace string) (*PlatformLinux, error) {
return &PlatformLinux{usr.Username, serviceNamespace, usrHomeDir}, nil
}

// TODO: validate these are correct

// GetUsername returns the username for the Linux OS.
func (p PlatformLinux) GetUsername() string {
return p.username
}

// GetUserHomeDir returns the user's home directory on the Linux OS.
func (p PlatformLinux) GetUserHomeDir() string {
// UserHomeDir returns the user's home directory on the Linux OS.
func (p PlatformLinux) UserHomeDir() string {
return p.userHomeDir
}

// GetDataDirectory returns the data directory for Linux.
func (p PlatformLinux) GetDataDirectory() string {
// UserAppDataDirectory returns the data directory for Linux.
// i.e. ~/.local/share/<serviceNamespace>
func (p PlatformLinux) UserAppDataDirectory() string {
return filepath.Join(p.userHomeDir, ".local", "share", p.serviceNamespace)
}

// GetConfigDirectory returns the config directory for Linux.
func (p PlatformLinux) GetConfigDirectory() string {
// UserAppConfigDirectory returns the config directory for Linux.
// i.e. ~/.config/<serviceNamespace>
func (p PlatformLinux) UserAppConfigDirectory() string {
return filepath.Join(p.userHomeDir, ".config", p.serviceNamespace)
}

// SystemAppDataDirectory returns the system-level data directory for Linux.
// i.e. /var/lib/<serviceNamespace>
func (p PlatformLinux) SystemAppDataDirectory() string {
return filepath.Join("/", "var", "lib", p.serviceNamespace)
}

// SystemAppConfigDirectory returns the system-level config directory for Linux.
// i.e. /etc/<serviceNamespace>
func (p PlatformLinux) SystemAppConfigDirectory() string {
return filepath.Join("/", "etc", p.serviceNamespace)
}
14 changes: 9 additions & 5 deletions pkg/platform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ type Platform interface {
// Get the username as known to the operating system
GetUsername() string
// Get the user's home directory
GetUserHomeDir() string
// Get the data directory for the platform
GetDataDirectory() string
// Get the config directory for the platform
GetConfigDirectory() string
UserHomeDir() string
// Get the namespaced user-level data directory for the platform
UserAppDataDirectory() string
// Get the namespaced user-level config directory for the platform
UserAppConfigDirectory() string
// Get the namespaced system-level data directory for the platform
SystemAppDataDirectory() string
// Get the namespaced system-level config directory for the platform
SystemAppConfigDirectory() string
}

// NewPlatform creates a new platform object based on the current operating system
Expand Down
73 changes: 64 additions & 9 deletions pkg/platform/platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,25 @@ func Test_PlatformMacOS(t *testing.T) {
require.True(t, ok)
require.NotNil(t, darwin)

configDir := darwin.GetConfigDirectory()
// user scoped
configDir := darwin.UserAppConfigDirectory()
assert.True(t, strings.HasSuffix(configDir, fakeAppName))
assert.True(t, strings.Contains(configDir, "/Library/Application Support"))
assert.False(t, strings.HasPrefix(configDir, "/Library/Application Support"))

dataDir := darwin.GetDataDirectory()
dataDir := darwin.UserAppDataDirectory()
assert.True(t, strings.HasSuffix(dataDir, fakeAppName))
assert.True(t, strings.Contains(dataDir, "Library/Application Support"))
assert.True(t, strings.Contains(dataDir, "/Library/Application Support"))
assert.False(t, strings.HasPrefix(configDir, "/Library/Application Support"))

// system scoped
configDir = darwin.SystemAppConfigDirectory()
assert.True(t, strings.HasSuffix(configDir, fakeAppName))
assert.True(t, strings.HasPrefix(configDir, "/Library/Application Support"))

dataDir = darwin.SystemAppDataDirectory()
assert.True(t, strings.HasSuffix(dataDir, fakeAppName))
assert.True(t, strings.HasPrefix(dataDir, "/Library/Application Support"))
}

func Test_PlatformLinux(t *testing.T) {
Expand All @@ -38,16 +51,41 @@ func Test_PlatformLinux(t *testing.T) {
require.True(t, ok)
require.NotNil(t, linux)

configDir := linux.GetConfigDirectory()
// user scoped
configDir := linux.UserAppConfigDirectory()
assert.True(t, strings.HasSuffix(configDir, fakeAppName))
assert.True(t, strings.Contains(configDir, "/.config"))
assert.False(t, strings.HasPrefix(configDir, "/.config"))
assert.False(t, strings.HasPrefix(configDir, "/etc"))

dataDir := linux.UserAppDataDirectory()
assert.True(t, strings.HasSuffix(dataDir, fakeAppName))
assert.True(t, strings.Contains(dataDir, "/.local/share"))
assert.False(t, strings.HasPrefix(dataDir, "/var/lib"))
assert.False(t, strings.HasPrefix(dataDir, "/.local/share"))

// system scoped
configDir = linux.SystemAppConfigDirectory()
assert.True(t, strings.HasSuffix(configDir, fakeAppName))
assert.True(t, strings.Contains(configDir, ".config"))
assert.True(t, strings.HasPrefix(configDir, "/etc"))

dataDir := linux.GetDataDirectory()
dataDir = linux.SystemAppDataDirectory()
assert.True(t, strings.HasSuffix(dataDir, fakeAppName))
assert.True(t, strings.Contains(dataDir, ".local/share"))
assert.True(t, strings.HasPrefix(dataDir, "/var/lib"))
}

// test utility to convert path to windows format and simplify cross-platform testing
func convertToWindowsPath(path string) string {
return strings.ReplaceAll(path, "/", `\`)
}

func Test_PlatformWindows(t *testing.T) {
fakeDrive := "FakeCDrive:\\"
// set up fake environment variables
t.Setenv(envKeyLocalAppData, fakeDrive+"Users\\test\\AppData\\Local")
t.Setenv(envKeyProgramData, fakeDrive+"ProgramData")
t.Setenv(envKeyProgramFiles, fakeDrive+"ProgramFiles")

fakeAppName := "test-windows-app"
plat, err := NewPlatform(fakeAppName, "windows")

Expand All @@ -58,8 +96,25 @@ func Test_PlatformWindows(t *testing.T) {
require.True(t, ok)
require.NotNil(t, windows)

configDir := windows.GetConfigDirectory()
// user scoped
configDir := windows.UserAppConfigDirectory()
assert.True(t, strings.HasSuffix(configDir, fakeAppName))
assert.True(t, strings.Contains(configDir, "AppData\\Local"))
assert.True(t, strings.HasPrefix(configDir, fakeDrive))

dataDir := windows.UserAppDataDirectory()
assert.True(t, strings.HasSuffix(dataDir, fakeAppName))
assert.True(t, strings.Contains(dataDir, "AppData\\Local"))
assert.True(t, strings.HasPrefix(dataDir, fakeDrive))

// system scoped
configDir = windows.SystemAppConfigDirectory()
configDir = convertToWindowsPath(configDir)
assert.True(t, strings.HasSuffix(configDir, fakeAppName))
assert.True(t, strings.HasPrefix(configDir, fakeDrive+"ProgramFiles\\"))

// TODO: tests according to different OS versions
dataDir = windows.SystemAppDataDirectory()
dataDir = convertToWindowsPath(dataDir)
assert.True(t, strings.HasSuffix(dataDir, fakeAppName))
assert.True(t, strings.HasPrefix(dataDir, fakeDrive+"ProgramData\\"))
}
72 changes: 57 additions & 15 deletions pkg/platform/windows.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package platform

import (
"fmt"
"os"
"os/user"
"path/filepath"
Expand All @@ -10,14 +11,23 @@ type PlatformWindows struct {
username string
serviceNamespace string
userHomeDir string
programFiles string
programData string
localAppData string
}

const (
envKeyLocalAppData = "LOCALAPPDATA"
envKeyProgramData = "PROGRAMDATA"
envKeyProgramFiles = "PROGRAMFILES"
envKeyUsername = "USERNAME"
)

func NewPlatformWindows(serviceNamespace string) (*PlatformWindows, error) {
// On Windows, use user.Current() if available, else fallback to environment variable
usr, err := user.Current()
if err != nil {
// TODO: test this on windows
usr = &user.User{Username: os.Getenv("USERNAME")}
usr = &user.User{Username: os.Getenv(envKeyUsername)}
if usr.Username == "" {
return nil, ErrGettingUserOS
}
Expand All @@ -26,30 +36,62 @@ func NewPlatformWindows(serviceNamespace string) (*PlatformWindows, error) {
if err != nil {
return nil, ErrGettingUserOS
}
return &PlatformWindows{usr.Username, serviceNamespace, usrHomeDir}, nil
}

// TODO: validate these are correct
programFiles := os.Getenv(envKeyProgramFiles)
if programFiles == "" {
return nil, fmt.Errorf("failed to detect %%%s%%: %w", envKeyProgramFiles, ErrGettingUserOS)
}

programData := os.Getenv(envKeyProgramData)
if programData == "" {
return nil, fmt.Errorf("failed to detect %%%s%%: %w", envKeyProgramData, ErrGettingUserOS)
}

localAppData := os.Getenv(envKeyLocalAppData)
if localAppData == "" {
return nil, fmt.Errorf("failed to detect %%%s%%: %w", envKeyLocalAppData, ErrGettingUserOS)
}

return &PlatformWindows{
username: usr.Username,
serviceNamespace: serviceNamespace,
userHomeDir: usrHomeDir,
programFiles: programFiles,
programData: programData,
localAppData: localAppData,
}, nil
}

// GetUsername returns the username for Windows.
func (p PlatformWindows) GetUsername() string {
return p.username
}

// GetUserHomeDir returns the user's home directory on Windows.
func (p PlatformWindows) GetUserHomeDir() string {
// UserHomeDir returns the user's home directory on Windows.
func (p PlatformWindows) UserHomeDir() string {
return p.userHomeDir
}

// TODO: it looks like this is different depending on OS version, so we should consider that
// https://learn.microsoft.com/en-us/windows/apps/design/app-settings/store-and-retrieve-app-data
// UserAppDataDirectory returns the namespaced user-level data directory for windows.
// i.e. %LocalAppData%\<serviceNamespace>
func (p PlatformWindows) UserAppDataDirectory() string {
return filepath.Join(p.localAppData, p.serviceNamespace)
}

// UserAppConfigDirectory returns the namespaced user-level config directory for windows.
// i.e. %LocalAppData%\<serviceNamespace>
func (p PlatformWindows) UserAppConfigDirectory() string {
return filepath.Join(p.localAppData, p.serviceNamespace)
}

// GetDataDirectory returns the data directory for Windows.
func (p PlatformWindows) GetDataDirectory() string {
return filepath.Join(p.userHomeDir, "AppData", "Roaming", p.serviceNamespace)
// SystemAppDataDirectory returns the namespaced system-level data directory for windows.
// %ProgramData%\<serviceNamespace>
func (p PlatformWindows) SystemAppDataDirectory() string {
return filepath.Join(p.programData, p.serviceNamespace)
}

// GetConfigDirectory returns the config directory for Windows.
func (p PlatformWindows) GetConfigDirectory() string {
return filepath.Join(p.userHomeDir, "AppData", "Local", p.serviceNamespace)
// SystemAppConfigDirectory returns the namespaced system-level config directory for windows.
// %ProgramFiles%\<serviceNamespace>
func (p PlatformWindows) SystemAppConfigDirectory() string {
return filepath.Join(p.programFiles, p.serviceNamespace)
}

0 comments on commit 1b18bde

Please sign in to comment.