Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v3] Added AppDataDirectory path #3823

Merged
47 changes: 47 additions & 0 deletions mkdocs-website/docs/en/API/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,53 @@ API: `Show()`
app.Show()
```

### Path

API: `Path(selector Path) string`

`Path(selector Path)` returns the full path for the given path type. It provides a cross-platform way to access common application directories.

The `Path` type is an enum with the following values:
- `AppData`: Returns the path to the application data directory
- `UserCache`: Returns the path to the user's cache directory
- `UserConfig`: Returns the path to the user's configuration directory

```go
// Get the application data directory path
appDataPath, err := app.Path(application.AppData)
if err != nil {
log.Fatal(err)
}
fmt.Println("AppData path:", appDataPath)

// Output: AppData path: /home/username/.local/share // Linux
// Output: AppData path: /Users/username/Library/Application Support // macOS
// Output: AppData path: C:\Users\Username\AppData\Roaming // Windows

// Get the user cache directory path
userCachePath, err := app.Path(application.UserCache)
if err != nil {
log.Fatal(err)
}
fmt.Println("UserCache path:", userCachePath)

// Output: UserCache path: /home/username/.cache // Linux
// Output: UserCache path: /Users/username/Library/Caches // macOS
// Output: UserCache path: C:\Users\Username\AppData\Local\Temp // Windows

// Get the user config directory path
userConfigPath, err := app.Path(application.UserConfig)
if err != nil {
log.Fatal(err)
}
fmt.Println("UserConfig path:", userConfigPath)

// Output: UserConfig path: /home/username/.config // Linux
// Output: UserConfig path: /Users/username/Library/Preferences // macOS
// Output: UserConfig path: C:\Users\Username\AppData\Local // Windows
```


--8<--
./docs/en/API/application_window.md
./docs/en/API/application_menu.md
Expand Down
1 change: 1 addition & 0 deletions mkdocs-website/docs/en/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Taskfile refactor by [leaanthony](https://github.com/leaanthony) in [#3748](https://github.com/wailsapp/wails/pull/3748)
- Upgrade to `go-webview2` v1.0.16 by [leaanthony](https://github.com/leaanthony)
- Fixed `Screen` type to include `ID` not `Id` by [etesam913](https://github.com/etesam913) in [#3778](https://github.com/wailsapp/wails/pull/3778)
- Add `Path` method to `application` package by [ansxuman](https://github.com/ansxuman) in [#3823](https://github.com/wailsapp/wails/pull/3823)

## v3.0.0-alpha.7 - 2024-09-18

Expand Down
3 changes: 2 additions & 1 deletion v3/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ require (
github.com/tc-hib/winres v0.3.1
github.com/wailsapp/go-webview2 v1.0.16
github.com/wailsapp/mimetype v1.4.1
golang.org/x/sys v0.20.0
golang.org/x/sys v0.22.0
golang.org/x/term v0.20.0
golang.org/x/tools v0.21.0
modernc.org/sqlite v1.21.0
Expand All @@ -46,6 +46,7 @@ require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/adrg/xdg v0.5.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/console v1.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
Expand Down
5 changes: 5 additions & 0 deletions v3/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCv
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
Expand Down Expand Up @@ -215,6 +217,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4=
github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/wailsapp/go-webview2 v1.0.16 h1:wffnvnkkLvhRex/aOrA3R7FP7rkvOqL/bir1br7BekU=
Expand Down Expand Up @@ -286,6 +289,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down
26 changes: 26 additions & 0 deletions v3/pkg/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ var alphaAssets embed.FS

var globalApplication *App

type Path int

const (
AppData Path = iota
UserCache
UserConfig
)

// AlphaAssets is the default assets for the alpha application
var AlphaAssets = AssetOptions{
Handler: BundledAssetFileServer(alphaAssets),
Expand Down Expand Up @@ -192,6 +200,9 @@ type (
GetFlags(options Options) map[string]any
isOnMainThread() bool
isDarkMode() bool
getAppDataPath() (string, error)
getUserCachePath() (string, error)
getUserConfigPath() (string, error)
}

runnable interface {
Expand Down Expand Up @@ -1013,3 +1024,18 @@ func (a *App) shouldQuit() bool {
}
return true
}

// Path returns the path for the given selector

func (a *App) Path(selector Path) (string, error) {
switch selector {
case AppData:
return a.impl.getAppDataPath()
case UserCache:
return a.impl.getUserCachePath()
case UserConfig:
return a.impl.getUserConfigPath()
default:
return "", fmt.Errorf("unknown path selector: %v", selector)
}
}
50 changes: 50 additions & 0 deletions v3/pkg/application/application_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,12 @@ static const char* serializationNSDictionary(void *dict) {
import "C"
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"unsafe"

"github.com/adrg/xdg"
"github.com/wailsapp/wails/v3/internal/operatingsystem"

"github.com/wailsapp/wails/v3/internal/assetserver/webview"
Expand Down Expand Up @@ -363,3 +367,49 @@ func (a *App) platformEnvironment() map[string]any {
func fatalHandler(errFunc func(error)) {
return
}

func (a *macosApp) getAppDataPath() (string, error) {
path := filepath.Join(xdg.DataHome, "Application Support")
if _, err := os.Stat(path); err == nil {
return path, nil
}

home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "Library", "Application Support"), nil
}

func (a *macosApp) getUserCachePath() (string, error) {
if _, err := os.Stat(xdg.CacheHome); err == nil {
return xdg.CacheHome, nil
}

home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "Library", "Caches"), nil
}

func (a *macosApp) getUserConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}

userPrefDir := filepath.Join(home, "Library", "Preferences")

for _, dir := range xdg.ConfigDirs {
if dir == userPrefDir {
return dir, nil
}
if strings.HasSuffix(dir, "Library/Preferences") {
if strings.HasPrefix(dir, home) {
return dir, nil
}
}
}
return userPrefDir, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Simplify and correct user configuration path retrieval

The getUserConfigPath function includes complex logic involving xdg.ConfigDirs to determine the user configuration directory. On macOS, configuration files are typically stored in ~/Library/Preferences. The usage of xdg.ConfigDirs may not be necessary and could introduce unexpected behavior.

Consider simplifying the function to directly return the macOS preferences directory:

func (a *macosApp) getUserConfigPath() (string, error) {
    home, err := os.UserHomeDir()
    if err != nil {
        return "", err
    }
    return filepath.Join(home, "Library", "Preferences"), nil
}

If there's a need to handle user-defined configuration directories, ensure that the logic accurately reflects macOS directory structures and standards.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (a *macosApp) getUserConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
userPrefDir := filepath.Join(home, "Library", "Preferences")
for _, dir := range xdg.ConfigDirs {
if dir == userPrefDir {
return dir, nil
}
if strings.HasSuffix(dir, "Library/Preferences") {
if strings.HasPrefix(dir, home) {
return dir, nil
}
}
}
return userPrefDir, nil
}
func (a *macosApp) getUserConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "Library", "Preferences"), nil
}

35 changes: 35 additions & 0 deletions v3/pkg/application/application_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import "C"
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"

"github.com/adrg/xdg"
"github.com/godbus/dbus/v5"
"github.com/wailsapp/wails/v3/internal/operatingsystem"
"github.com/wailsapp/wails/v3/pkg/events"
Expand Down Expand Up @@ -260,3 +262,36 @@ func fatalHandler(errFunc func(error)) {
// Stub for windows function
return
}

func (a *linuxApp) getAppDataPath() (string, error) {
if xdg.DataHome != "" {
return xdg.DataHome, nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".local", "share"), nil
}

func (a *linuxApp) getUserCachePath() (string, error) {
if xdg.CacheHome != "" {
return xdg.CacheHome, nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".cache"), nil
}

func (a *linuxApp) getUserConfigPath() (string, error) {
if xdg.ConfigHome != "" {
return xdg.ConfigHome, nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".config"), nil
}
26 changes: 26 additions & 0 deletions v3/pkg/application/application_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package application

import (
"fmt"
"os"
"path/filepath"
"sync"
"syscall"
"unsafe"
Expand Down Expand Up @@ -349,3 +351,27 @@ func fatalHandler(errFunc func(error)) {
w32.Fatal = errFunc
return
}

func (a *windowsApp) getAppDataPath() (string, error) {
path := os.Getenv("APPDATA")
if path == "" {
return "", fmt.Errorf("APPDATA environment variable is not set")
}
return path, nil
}

func (a *windowsApp) getUserCachePath() (string, error) {
localAppData := os.Getenv("LOCALAPPDATA")
if localAppData == "" {
return "", fmt.Errorf("LOCALAPPDATA environment variable is not set")
}
return filepath.Join(localAppData, "Temp"), nil
}

func (a *windowsApp) getUserConfigPath() (string, error) {
path := os.Getenv("LOCALAPPDATA")
if path == "" {
return "", fmt.Errorf("LOCALAPPDATA environment variable is not set")
}
return path, nil
}