Skip to content

Commit

Permalink
Add XDG support for siegfried home directory (#221)
Browse files Browse the repository at this point in the history
* add support for XDG and allow overriding siegfried home via environment variable

Uses github.com/adrg/xdg for mapping the right data directory per platform.
This means, unless a siegfried home directory already exists for the current user,
it will now be created in:

Linux: "~/$XDG_DATA_HOME/siegfried" (or "~/.local/share/siegfried")
macOS: "~/Library/Application Support/siegfried"
Windows: "%LOCALAPPDATA%"

* move away from deprecated ioutil package and use newer error type checking method

* use local implementation for finding the user data directory per platform

* add missing windows import

* fix wrong variable scope for Windows path initialization
  • Loading branch information
prettybits committed May 27, 2023
1 parent 6c1f379 commit 6ca20bb
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 38 deletions.
6 changes: 4 additions & 2 deletions cmd/sf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io/fs"
"io/ioutil"
"os"
"strings"
Expand Down Expand Up @@ -59,7 +61,7 @@ func setconf() (string, error) {
}
// no flags - so we delete the conf file if it exists
if _, err := os.Stat(config.Conf()); err != nil {
if os.IsNotExist(err) {
if errors.Is(err, fs.ErrNotExist) {
return "", nil
}
return "", err
Expand All @@ -70,7 +72,7 @@ func setconf() (string, error) {
// if it exists, read defaults from the conf file.
func getconf() (map[string]string, error) {
if _, err := os.Stat(config.Conf()); err != nil {
if os.IsNotExist(err) {
if errors.Is(err, fs.ErrNotExist) {
return nil, nil
}
return nil, err
Expand Down
4 changes: 3 additions & 1 deletion cmd/sf/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/fs"
"io/ioutil"
"net/http"
"os"
Expand Down Expand Up @@ -118,7 +120,7 @@ func updateSigs(sig string, args []string) (bool, string, error) {
}
// this hairy bit of golang exception handling is thanks to Ross! :)
if _, err = os.Stat(config.Home()); err != nil {
if os.IsNotExist(err) {
if errors.Is(err, fs.ErrNotExist) {
err = os.MkdirAll(config.Home(), os.ModePerm)
if err != nil {
return false, "", fmt.Errorf("Siegfried: cannot create home directory %s, %v", config.Home(), err)
Expand Down
50 changes: 44 additions & 6 deletions pkg/config/default.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !brew && !archivematica && !js
// +build !brew,!archivematica,!js

// Copyright 2014 Richard Lehane. All rights reserved.
Expand All @@ -17,16 +18,53 @@
package config

import (
"errors"
"io/fs"
"log"
"os/user"
"os"
"path/filepath"
"strings"
)

// the default Home location is a "siegfried" folder in the user's $HOME
// the default Home location is a "siegfried" folder in the user's application data folder, which can be overridden by setting the SIEGFRIED_HOME environment variable
func init() {
current, err := user.Current()
if err != nil {
log.Fatal(err)
if home, ok := os.LookupEnv("SIEGFRIED_HOME"); ok {
siegfried.home = home
} else {
home, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}

// if a home directory already exists in the legacy location continue using it, otherwise default to a XDG-aware OS-specific application data directory
siegfried.home = filepath.Join(home, "siegfried")
if _, err := os.Stat(siegfried.home); err != nil {
if errors.Is(err, fs.ErrNotExist) {
siegfried.home = filepath.Join(userDataDir(home), "siegfried")
} else {
log.Fatal(err)
}
}
}
}

func xdgPath(home string, defaultPath string) string {
dataHome, found := os.LookupEnv("XDG_DATA_HOME")
if found && dataHome != "" {
if dataDir, found := strings.CutPrefix(dataHome, "~"); found {

Check failure on line 54 in pkg/config/default.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.19)

undefined: strings.CutPrefix

Check failure on line 54 in pkg/config/default.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.18)

undefined: strings.CutPrefix

Check failure on line 54 in pkg/config/default.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.18)

undefined: strings.CutPrefix
dataHome = filepath.Join(home, dataDir)
}

// environment variable might contain variables like $HOME itself, let's expand
dataHome = os.ExpandEnv(dataHome)
}

// XDG Base Directory Specification demands relative paths to be ignored, fall back to default in that case
if filepath.IsAbs(dataHome) {
return dataHome
} else if filepath.IsAbs(defaultPath) {
return defaultPath
} else {
return filepath.Join(home, defaultPath)
}
siegfried.home = filepath.Join(current.HomeDir, "siegfried")
}
27 changes: 27 additions & 0 deletions pkg/config/default_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build darwin && !brew && !archivematica && !js
// +build darwin,!brew,!archivematica,!js

// Copyright 2014 Richard Lehane. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
"path/filepath"
)


func userDataDir(home string) string {

Check failure on line 25 in pkg/config/default_darwin.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.x)

other declaration of userDataDir

Check failure on line 25 in pkg/config/default_darwin.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.20)

other declaration of userDataDir

Check failure on line 25 in pkg/config/default_darwin.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.18)

other declaration of userDataDir
return xdgPath(home, filepath.Join("Library", "Application Support"))
}
23 changes: 23 additions & 0 deletions pkg/config/default_plan9.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//go:build plan9 && !brew && !archivematica && !js
// +build plan9,!brew,!archivematica,!js

// Copyright 2014 Richard Lehane. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config


func userDataDir(home string) string {
return xdgPath(home, "lib")
}
26 changes: 26 additions & 0 deletions pkg/config/default_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build (aix || darwin || dragonfly || freebsd || nacl || linux || netbsd || openbsd || solaris) && !brew && !archivematica && !js
// +build aix darwin dragonfly freebsd nacl linux netbsd openbsd solaris
// +build !brew
// +build !archivematica
// +build !js

// Copyright 2014 Richard Lehane. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config


func userDataDir(home string) string {

Check failure on line 24 in pkg/config/default_unix.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.x)

userDataDir redeclared in this block

Check failure on line 24 in pkg/config/default_unix.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.20)

userDataDir redeclared in this block

Check failure on line 24 in pkg/config/default_unix.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.18)

userDataDir redeclared in this block
return xdgPath(home, ".local/share")
}
44 changes: 44 additions & 0 deletions pkg/config/default_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//go:build windows && !brew && !archivematica && !js
// +build windows,!brew,!archivematica,!js

// Copyright 2014 Richard Lehane. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
"os"
"path/filepath"

"golang.org/x/sys/windows"
)


func userDataDir(home string) string {
path, _ := windows.KnownFolderPath(*windows.FOLDERID_LocalAppData, windows.KF_FLAG_DEFAULT|KF_FLAG_DONT_VERIFY)
if path == "" {
path, _ = windows.KnownFolderPath(*windows.FOLDERID_LocalAppData, windows.KF_FLAG_DEFAULT_PATH|KF_FLAG_DONT_VERIFY)
}

if path == "" {
dataDir, found := os.LookupEnv("LOCALAPPDATA")
if found && dataDir != "" {
path = dataDir
} else {
path = filepath.Join("AppData", "Local")
}
}

return xdgPath(home, path)
}
4 changes: 2 additions & 2 deletions pkg/config/mimeinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ package config

import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
)

Expand Down Expand Up @@ -49,7 +49,7 @@ func MIMEInfo() string {
}

func MIMEVersion() []string {
byt, err := ioutil.ReadFile(filepath.Join(siegfried.home, mimeinfo.versions))
byt, err := os.ReadFile(filepath.Join(siegfried.home, mimeinfo.versions))
m := make(map[string][]string)
if err == nil {
err = json.Unmarshal(byt, &m)
Expand Down
6 changes: 3 additions & 3 deletions pkg/config/pronom.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ package config

import (
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
Expand Down Expand Up @@ -125,7 +125,7 @@ func ContainerBase() string {
func latest(prefix, suffix string) (string, error) {
var hits []string
var ids []int
files, err := ioutil.ReadDir(siegfried.home)
files, err := os.ReadDir(siegfried.home)
if err != nil {
return "", err
}
Expand All @@ -141,7 +141,7 @@ func latest(prefix, suffix string) (string, error) {
}
}
if len(hits) == 0 {
return "", fmt.Errorf("Config: no file in %s with prefix %s", siegfried.home, prefix)
return "", fmt.Errorf("config: no file in %s with prefix %s", siegfried.home, prefix)
}
if len(hits) == 1 {
return hits[0], nil
Expand Down
23 changes: 13 additions & 10 deletions pkg/config/wikidata.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
package config

import (
"errors"
"fmt"
"io/fs"
"net/url"
"os"
"path/filepath"
Expand Down Expand Up @@ -324,7 +326,7 @@ func SetWikibaseSparql(query string) func() private {
func checkWikibaseURL(customEndpointURL string, customWikibaseURL string) error {
if customWikibaseURL == WikidataWikibaseURL() {
return fmt.Errorf(
"Wikibase server URL for '%s' needs to be configured, can't be: '%s'",
"wikibase server URL for '%s' needs to be configured, can't be: '%s'",
customEndpointURL,
customWikibaseURL,
)
Expand Down Expand Up @@ -355,17 +357,18 @@ func SetCustomWikibaseEndpoint(customEndpointURL string, customWikibaseURL strin
func SetCustomWikibaseQuery() error {
wikibaseSparqlPath := WikibaseSparqlFile()
sparqlFile, err := os.ReadFile(wikibaseSparqlPath)
if os.IsNotExist(err) {
return fmt.Errorf(
"Setting custom Wikibase SPARQL: cannot find file '%s' in '%s': %w",
wikibaseSparqlPath,
WikidataHome(),
err,
)
}
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf(
"setting custom Wikibase SPARQL: cannot find file '%s' in '%s': %w",
wikibaseSparqlPath,
WikidataHome(),
err,
)
}

return fmt.Errorf(
"Setting custom Wikibase SPARQL: unexpected error opening '%s' has occurred: %w",
"setting custom Wikibase SPARQL: unexpected error opening '%s' has occurred: %w",
wikibaseSparqlPath,
err,
)
Expand Down
13 changes: 7 additions & 6 deletions pkg/config/wikidata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package config
import (
"errors"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -42,15 +41,17 @@ func TestProps(t *testing.T) {
// handling required for updating our SPARQL query for a custom Wikibase.
func TestSetCustomWikibaseQuery(t *testing.T) {
var testSPARQL = "select ?s ?p ?o where { ?s ?p ?o. }"
tempDir, _ := ioutil.TempDir("", "wikidata-test-dir-*")
defer os.RemoveAll(tempDir)
err := os.Mkdir(filepath.Join(tempDir, "wikidata"), 0755)
tempDir, err := os.MkdirTemp("", "wikidata-test-dir-*")
if err != nil {
t.Fatal(err)
}
err = os.Mkdir(filepath.Join(tempDir, "wikidata"), 0755)
if err != nil {
t.Fatal(err)
}
SetHome(tempDir)
customSPARQLFile := filepath.Join(tempDir, "wikidata", "wikibase.sparql")
err = ioutil.WriteFile(customSPARQLFile, []byte(testSPARQL), 0755)
err = os.WriteFile(customSPARQLFile, []byte(testSPARQL), 0755)
if err != nil {
t.Fatal(err)
}
Expand All @@ -67,7 +68,7 @@ func TestSetCustomWikibaseQuery(t *testing.T) {
WikidataSPARQL(),
)
}
err = os.Remove(customSPARQLFile)
_ = os.Remove(customSPARQLFile)
err = SetCustomWikibaseQuery()
if !errors.Is(err, fs.ErrNotExist) {
t.Errorf(
Expand Down
Loading

0 comments on commit 6ca20bb

Please sign in to comment.