Expand ~ in receiveDir everywhere it becomes a filesystem path
A receiveDir stored as "~/Omarchy-Send" (hand-edited, or typed into the Settings tab) was treated as a relative path, so the receiver silently created a literal "~" directory under its cwd and wrote incoming files there. The TUI expanded ~ for display only, which hid the problem. The canonical ExpandHome now lives in config and is applied in Load (normalised value is persisted back), in the Settings-tab save, and by the TUI's display helper, which now delegates to it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
38f057db67
commit
a93cdcaa83
3 changed files with 105 additions and 15 deletions
|
|
@ -6,11 +6,31 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"omarchy-send/internal/protocol"
|
"omarchy-send/internal/protocol"
|
||||||
"omarchy-send/internal/security"
|
"omarchy-send/internal/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ExpandHome resolves a leading "~" or "~/" to the user's home directory, so a
|
||||||
|
// receiveDir stored as "~/Omarchy-Send" (hand-edited, or typed into the
|
||||||
|
// Settings tab) means what the user means — and is not treated as a relative
|
||||||
|
// path that silently creates a literal "~" directory under the process cwd.
|
||||||
|
func ExpandHome(p string) string {
|
||||||
|
if p == "~" {
|
||||||
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
|
return home
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(p, "~/") {
|
||||||
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
|
return filepath.Join(home, p[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
// Config is the persisted user configuration.
|
// Config is the persisted user configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Alias string `json:"alias"`
|
Alias string `json:"alias"`
|
||||||
|
|
@ -106,6 +126,9 @@ func Load() (Config, error) {
|
||||||
if cfg.ReceiveDir == "" {
|
if cfg.ReceiveDir == "" {
|
||||||
cfg.ReceiveDir = d.ReceiveDir
|
cfg.ReceiveDir = d.ReceiveDir
|
||||||
}
|
}
|
||||||
|
// Normalise a ~-form receive dir to absolute; Load persists below, so the
|
||||||
|
// stored value is unambiguous from then on.
|
||||||
|
cfg.ReceiveDir = ExpandHome(cfg.ReceiveDir)
|
||||||
if cfg.DeviceType == "" {
|
if cfg.DeviceType == "" {
|
||||||
cfg.DeviceType = d.DeviceType
|
cfg.DeviceType = d.DeviceType
|
||||||
}
|
}
|
||||||
|
|
|
||||||
76
internal/config/config_test.go
Normal file
76
internal/config/config_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExpandHome resolves the ~-forms a user may type or hand-edit, and leaves
|
||||||
|
// everything else untouched.
|
||||||
|
func TestExpandHome(t *testing.T) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("no home dir: %v", err)
|
||||||
|
}
|
||||||
|
cases := map[string]string{
|
||||||
|
"~": home,
|
||||||
|
"~/Omarchy-Send": filepath.Join(home, "Omarchy-Send"),
|
||||||
|
"~/a/b": filepath.Join(home, "a", "b"),
|
||||||
|
"/abs/path": "/abs/path",
|
||||||
|
"relative/path": "relative/path",
|
||||||
|
"~user/not-ours": "~user/not-ours", // ~user expansion is not supported
|
||||||
|
"mid/~/not-leading": "mid/~/not-leading",
|
||||||
|
"": "",
|
||||||
|
}
|
||||||
|
for in, want := range cases {
|
||||||
|
if got := ExpandHome(in); got != want {
|
||||||
|
t.Errorf("ExpandHome(%q) = %q, want %q", in, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A config file whose receiveDir was stored as "~/…" is normalised to an
|
||||||
|
// absolute path by Load — the regression that sent files into a literal "~"
|
||||||
|
// directory under the process cwd.
|
||||||
|
func TestLoadExpandsTildeReceiveDir(t *testing.T) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("no home dir: %v", err)
|
||||||
|
}
|
||||||
|
cfgHome := t.TempDir()
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", cfgHome)
|
||||||
|
|
||||||
|
dir := filepath.Join(cfgHome, "omarchy-send")
|
||||||
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
|
t.Fatalf("mkdir: %v", err)
|
||||||
|
}
|
||||||
|
seed := map[string]any{"alias": "t", "receiveDir": "~/Omarchy-Send"}
|
||||||
|
data, _ := json.Marshal(seed)
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "config.json"), data, 0o600); err != nil {
|
||||||
|
t.Fatalf("seed config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
want := filepath.Join(home, "Omarchy-Send")
|
||||||
|
if cfg.ReceiveDir != want {
|
||||||
|
t.Fatalf("ReceiveDir = %q, want %q", cfg.ReceiveDir, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And the normalised value is what got persisted back.
|
||||||
|
raw, err := os.ReadFile(filepath.Join(dir, "config.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read back: %v", err)
|
||||||
|
}
|
||||||
|
var onDisk map[string]any
|
||||||
|
if err := json.Unmarshal(raw, &onDisk); err != nil {
|
||||||
|
t.Fatalf("unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if onDisk["receiveDir"] != want {
|
||||||
|
t.Fatalf("persisted receiveDir = %q, want %q", onDisk["receiveDir"], want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -734,7 +734,9 @@ func (m Model) saveEdit() (tea.Model, tea.Cmd) {
|
||||||
m.cfg.DeviceModel = alias
|
m.cfg.DeviceModel = alias
|
||||||
}
|
}
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
m.cfg.ReceiveDir = dir
|
// Expand a typed ~-form immediately so the live server and the saved
|
||||||
|
// config both carry the absolute path.
|
||||||
|
m.cfg.ReceiveDir = config.ExpandHome(dir)
|
||||||
}
|
}
|
||||||
m.cfg.PIN = pin
|
m.cfg.PIN = pin
|
||||||
_ = m.cfg.Save()
|
_ = m.cfg.Save()
|
||||||
|
|
@ -1354,21 +1356,10 @@ func collapseHome(p string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandHome resolves a leading ~ (or ~/) to the user's home directory. It is
|
// expandHome resolves a leading ~ (or ~/) to the user's home directory. It is
|
||||||
// the inverse of collapseHome and tolerates the ~-form a user may type into the
|
// the inverse of collapseHome; the canonical implementation lives in config so
|
||||||
// receive-dir setting.
|
// every consumer of ReceiveDir expands the same way.
|
||||||
func expandHome(p string) string {
|
func expandHome(p string) string {
|
||||||
if p == "~" {
|
return config.ExpandHome(p)
|
||||||
if home, err := os.UserHomeDir(); err == nil {
|
|
||||||
return home
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(p, "~/") {
|
|
||||||
if home, err := os.UserHomeDir(); err == nil {
|
|
||||||
return filepath.Join(home, p[2:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func truncate(s string, n int) string {
|
func truncate(s string, n int) string {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue