Add new AppDataDir function.

This commit adds a new AppDataDir function that can be used to get an
operating system and application specific directory to be used for storing
application data.

For example:

dir := AppDataDir("myapp", false)

Would result in:

POSIX (Linux/BSD): ~/.myapp
Mac OS: $HOME/Library/Application Support/Myapp
Windows: %LOCALAPPDATA%\Myapp
Plan 9: $home/myapp

This is work toward conformal/btcd#30.
This commit is contained in:
Dave Collins 2013-11-10 12:27:39 -06:00
parent ccb6557298
commit fcf9b83256
3 changed files with 245 additions and 0 deletions

107
appdata.go Normal file
View file

@ -0,0 +1,107 @@
// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcutil
import (
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
"unicode"
)
// appDataDir returns an operating system specific directory to be used for
// storing application data for an application. See AppDataDir for more
// details. This unexported version takes an operating system argument
// primarily to enable the testing package to properly test the function by
// forcing an operating system that is not the currently one.
func appDataDir(goos, appName string, roaming bool) string {
if appName == "" || appName == "." {
return "."
}
// The caller really shouldn't prepend the appName with a period, but
// if they do, handle it gracefully by stripping it.
if strings.HasPrefix(appName, ".") {
appName = appName[1:]
}
appNameUpper := string(unicode.ToUpper(rune(appName[0]))) + appName[1:]
appNameLower := string(unicode.ToLower(rune(appName[0]))) + appName[1:]
// Get the OS specific home directory via the Go standard lib.
var homeDir string
usr, err := user.Current()
if err == nil {
homeDir = usr.HomeDir
}
// Fall back to standard HOME environment variable that works
// for most POSIX OSes if the directory from the Go standard
// lib failed.
if err != nil || homeDir == "" {
homeDir = os.Getenv("HOME")
}
switch goos {
// Attempt to use the LOCALAPPDATA or APPDATA environment variable on
// Windows.
case "windows":
// Windows XP and before didn't have a LOCALAPPDATA, so fallback
// to regular APPDATA when LOCALAPPDATA is not set.
appData := os.Getenv("LOCALAPPDATA")
if roaming || appData == "" {
appData = os.Getenv("APPDATA")
}
if appData != "" {
return filepath.Join(appData, appNameUpper)
}
case "darwin":
if homeDir != "" {
return filepath.Join(homeDir, "Library",
"Application Support", appNameUpper)
}
case "plan9":
if homeDir != "" {
return filepath.Join(homeDir, appNameLower)
}
default:
if homeDir != "" {
return filepath.Join(homeDir, "."+appNameLower)
}
}
// Fall back to the current directory if all else fails.
return "."
}
// AppDataDir returns an operating system specific directory to be used for
// storing application data for an application.
//
// The appName parameter is the name of the application the data directory is
// being requested for. This function will prepend a period to the appName for
// POSIX style operating systems since that is standard practice. An empty
// appName or one with a single dot is treated as requesting the current
// directory so only "." will be returned. Further, the first character
// of appName will be made lowercase for POSIX style operating systems and
// uppercase for Mac and Windows since that is standard practice.
//
// The roaming parameter only applies to Windows where it specifies the roaming
// application data profile (%APPDATA%) should be used instead of the local one
// (%LOCALAPPDATA%) that is used by default.
//
// Example results:
// dir := AppDataDir("myapp", false)
// POSIX (Linux/BSD): ~/.myapp
// Mac OS: $HOME/Library/Application Support/Myapp
// Windows: %LOCALAPPDATA%\Myapp
// Plan 9: $home/myapp
func AppDataDir(appName string, roaming bool) string {
return appDataDir(runtime.GOOS, appName, roaming)
}

132
appdata_test.go Normal file
View file

@ -0,0 +1,132 @@
// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcutil_test
import (
"github.com/conformal/btcutil"
"os"
"os/user"
"path/filepath"
"runtime"
"testing"
"unicode"
)
// TestAppDataDir tests the API for AppDataDir to ensure it gives expected
// results for various operating systems.
func TestAppDataDir(t *testing.T) {
// App name plus upper and lowercase variants.
appName := "myapp"
appNameUpper := string(unicode.ToUpper(rune(appName[0]))) + appName[1:]
appNameLower := string(unicode.ToLower(rune(appName[0]))) + appName[1:]
// When we're on Windows, set the expected local and roaming directories
// per the environment vars. When we aren't on Windows, the function
// should return the current directory when forced to provide the
// Windows path since the environment variables won't exist.
winLocal := "."
winRoaming := "."
if runtime.GOOS == "windows" {
localAppData := os.Getenv("LOCALAPPDATA")
roamingAppData := os.Getenv("APPDATA")
if localAppData == "" {
localAppData = roamingAppData
}
winLocal = filepath.Join(localAppData, appNameUpper)
winRoaming = filepath.Join(roamingAppData, appNameUpper)
}
// Get the home directory to use for testing expected results.
var homeDir string
usr, err := user.Current()
if err != nil {
t.Errorf("user.Current: %v", err)
return
}
homeDir = usr.HomeDir
// Mac app data directory.
macAppData := filepath.Join(homeDir, "Library", "Application Support")
tests := []struct {
goos string
appName string
roaming bool
want string
}{
// Various combinations of application name casing, leading
// period, operating system, and roaming flags.
{"windows", appNameLower, false, winLocal},
{"windows", appNameUpper, false, winLocal},
{"windows", "." + appNameLower, false, winLocal},
{"windows", "." + appNameUpper, false, winLocal},
{"windows", appNameLower, true, winRoaming},
{"windows", appNameUpper, true, winRoaming},
{"windows", "." + appNameLower, true, winRoaming},
{"windows", "." + appNameUpper, true, winRoaming},
{"linux", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"linux", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"linux", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"linux", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"darwin", appNameLower, false, filepath.Join(macAppData, appNameUpper)},
{"darwin", appNameUpper, false, filepath.Join(macAppData, appNameUpper)},
{"darwin", "." + appNameLower, false, filepath.Join(macAppData, appNameUpper)},
{"darwin", "." + appNameUpper, false, filepath.Join(macAppData, appNameUpper)},
{"openbsd", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"openbsd", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"openbsd", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"openbsd", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"freebsd", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"freebsd", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"freebsd", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"freebsd", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"netbsd", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"netbsd", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"netbsd", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"netbsd", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"plan9", appNameLower, false, filepath.Join(homeDir, appNameLower)},
{"plan9", appNameUpper, false, filepath.Join(homeDir, appNameLower)},
{"plan9", "." + appNameLower, false, filepath.Join(homeDir, appNameLower)},
{"plan9", "." + appNameUpper, false, filepath.Join(homeDir, appNameLower)},
{"unrecognized", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"unrecognized", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
{"unrecognized", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)},
{"unrecognized", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)},
// No application name provided, so expect current directory.
{"windows", "", false, "."},
{"windows", "", true, "."},
{"linux", "", false, "."},
{"darwin", "", false, "."},
{"openbsd", "", false, "."},
{"freebsd", "", false, "."},
{"netbsd", "", false, "."},
{"plan9", "", false, "."},
{"unrecognized", "", false, "."},
// Single dot provided for application name, so expect current
// directory.
{"windows", ".", false, "."},
{"windows", ".", true, "."},
{"linux", ".", false, "."},
{"darwin", ".", false, "."},
{"openbsd", ".", false, "."},
{"freebsd", ".", false, "."},
{"netbsd", ".", false, "."},
{"plan9", ".", false, "."},
{"unrecognized", ".", false, "."},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
ret := btcutil.TstAppDataDir(test.goos, test.appName, test.roaming)
if ret != test.want {
t.Errorf("appDataDir #%d (%s) does not match - "+
"expected got %s, want %s", i, test.goos, ret,
test.want)
continue
}
}
}

View file

@ -17,3 +17,9 @@ package btcutil
func (b *Block) SetBlockBytes(buf []byte) {
b.serializedBlock = buf
}
// TstAppDataDir makes the internal appDataDir function available to the test
// package.
func TstAppDataDir(goos, appName string, roaming bool) string {
return appDataDir(goos, appName, roaming)
}