diff --git a/backend/system/userdata.go b/backend/system/userdata.go index 39dde59..9f232fe 100644 --- a/backend/system/userdata.go +++ b/backend/system/userdata.go @@ -29,6 +29,29 @@ const ( // without touching the current user's real config.toml or yj.db. const envHomeOverride = "YJ_HOME" +// UseHomeOverride points every config and data path at base, by setting +// the same override a development sandbox uses. +// +// It exists for mobile, where the switch in buildUserDirPath has no +// answer: there is no home directory and no XDG, only a per-app private +// directory the OS hands out at runtime. The caller is main(), which is +// the only place that can ask the platform for it — deliberately, so +// this package stays free of the Wails application package that knows +// (see backend/events' indexbuild split for why that matters). +// +// Two rules. An empty base is a no-op, because that is exactly what +// application.Mobile.StoragePath() returns on desktop. And an override +// that is already set wins, so YJ_HOME on the command line still +// relocates a sandbox on a platform that would otherwise decide for +// itself. +func UseHomeOverride(base string) { + if base == "" || os.Getenv(envHomeOverride) != "" { + return + } + + _ = os.Setenv(envHomeOverride, base) +} + // getUserDirPath returns and creates the path for a user directory. func getUserDirPath(dt dirType) (string, error) { path, err := resolveUserDirPath(dt) diff --git a/backend/system/userdata_test.go b/backend/system/userdata_test.go index b24abc0..3829360 100644 --- a/backend/system/userdata_test.go +++ b/backend/system/userdata_test.go @@ -1,6 +1,7 @@ package system import ( + "os" "path/filepath" "strings" "testing" @@ -50,3 +51,39 @@ func TestResolveUserDirPath_NoOverrideUsesOSPath(t *testing.T) { ) } } + +// UseHomeOverride carries two rules that a mobile launch depends on and +// that nothing else would notice breaking: an empty base must do +// nothing, because that is precisely what StoragePath() returns on +// desktop, and an override already set must win, or YJ_HOME would stop +// relocating a sandbox on the platform that decides for itself. +func TestUseHomeOverride(t *testing.T) { + const ( + storage = "/data/user/0/app.yellowjacket/files" + sandbox = "/tmp/sandbox" + ) + + tests := []struct { + name string + already string + base string + want string + }{ + {name: "empty base is a no-op", already: "", base: "", want: ""}, + {name: "sets the override when unset", already: "", base: storage, want: storage}, + {name: "an existing override wins", already: sandbox, base: storage, want: sandbox}, + {name: "empty base keeps an existing override", already: sandbox, base: "", want: sandbox}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(envHomeOverride, tt.already) + + UseHomeOverride(tt.base) + + if got := os.Getenv(envHomeOverride); got != tt.want { + t.Errorf("%s = %q, want %q", envHomeOverride, got, tt.want) + } + }) + } +} diff --git a/main.go b/main.go index 22553f9..5c593d4 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "yellowjacket/backend/assets" "yellowjacket/backend/config" "yellowjacket/backend/profiling" + "yellowjacket/backend/system" "yellowjacket/internal/dev" ) @@ -28,6 +29,20 @@ var ( var frontendDistAssets embed.FS func main() { + // **Mobile has no home directory, and this must run before anything + // asks for a path.** backend/system resolves config and data from + // $HOME or the OS equivalent, and on Android there is neither: its + // switch on runtime.GOOS took the default branch and returned + // errUnsupportedOS, so NewYellowJacketApp failed and main() exited + // six milliseconds after the JNI bridge came up -- with no panic and + // no tombstone, because os.Exit is not a crash. + // + // StoragePath() is the platform's own answer (getFilesDir() on + // Android, Application Support on iOS) and returns "" on desktop, + // where UseHomeOverride is then a no-op -- so this needs no build + // tag and changes nothing off mobile. + system.UseHomeOverride(application.Mobile.StoragePath()) + // WebKitGTK's DMABuf renderer crashes on NVIDIA GPUs under Wayland. // Only disable it for that specific combo so AMD/Intel and X11 users // keep full hardware-accelerated buffer sharing. Users can also