diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-08 22:07:25 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-08 22:07:25 +0300 |
| commit | 42226b12e7a172810bf785cef4cbb4fb41e2da3d (patch) | |
| tree | 96ad6868d4609192ba522a3b10481bb0ab1933c3 /internal | |
| parent | 595075473a51b2709f1554f055df43a69369cddd (diff) | |
task a: fix temp-file cleanup on desc write failure
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/ui/table.go | 42 | ||||
| -rw-r--r-- | internal/ui/table_test.go | 37 |
2 files changed, 68 insertions, 11 deletions
diff --git a/internal/ui/table.go b/internal/ui/table.go index 1314eb1..ae32070 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -204,6 +204,12 @@ type descEditDoneMsg struct { type blinkMsg struct{} +type descriptionTempFile interface { + Name() string + WriteString(string) (int, error) + Close() error +} + // blinkInterval controls how quickly the row flashes when a task changes. // A shorter interval results in a faster blink. const blinkInterval = 150 * time.Millisecond @@ -212,6 +218,28 @@ const blinkInterval = 150 * time.Millisecond // The total blink duration is blinkInterval * blinkCycles. const blinkCycles = 8 +func prepareDescriptionTempFile(description string, newTempFile func() (descriptionTempFile, error)) (string, error) { + tmpFile, err := newTempFile() + if err != nil { + return "", err + } + + tmpPath := tmpFile.Name() + + _, writeErr := tmpFile.WriteString(description) + closeErr := tmpFile.Close() + if writeErr != nil { + _ = os.Remove(tmpPath) + return "", writeErr + } + if closeErr != nil { + _ = os.Remove(tmpPath) + return "", closeErr + } + + return tmpPath, nil +} + // editCmd returns a command that edits the task and sends an // editDoneMsg once the process is complete. func editCmd(id int) tea.Cmd { @@ -222,18 +250,10 @@ func editCmd(id int) tea.Cmd { // editDescriptionCmd returns a command that opens the description in external editor func editDescriptionCmd(description string) tea.Cmd { return func() tea.Msg { - // Create temp file - tmpFile, err := os.CreateTemp("", "tasksamurai-desc-*.txt") - if err != nil { - return descEditDoneMsg{err: err, tempFile: ""} - } - tmpPath := tmpFile.Name() - - // Write current description to temp file - _, err = tmpFile.WriteString(description) - _ = tmpFile.Close() + tmpPath, err := prepareDescriptionTempFile(description, func() (descriptionTempFile, error) { + return os.CreateTemp("", "tasksamurai-desc-*.txt") + }) if err != nil { - _ = os.Remove(tmpPath) return descEditDoneMsg{err: err, tempFile: ""} } diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index dccec31..d8c9db4 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -200,6 +200,27 @@ func TestHandleDescEditDoneUpdatesDescriptionAndRemovesTempFile(t *testing.T) { } } +func TestPrepareDescriptionTempFileRemovesTempFileOnWriteError(t *testing.T) { + tmp := t.TempDir() + tempFile := filepath.Join(tmp, "desc.txt") + if err := os.WriteFile(tempFile, []byte("stale"), 0o644); err != nil { + t.Fatal(err) + } + + path, err := prepareDescriptionTempFile("updated description", func() (descriptionTempFile, error) { + return &failingDescriptionTempFile{path: tempFile}, nil + }) + if err == nil { + t.Fatalf("prepareDescriptionTempFile succeeded unexpectedly: %q", path) + } + if path != "" { + t.Fatalf("expected empty path on error, got %q", path) + } + if _, statErr := os.Stat(tempFile); !os.IsNotExist(statErr) { + t.Fatalf("temp file still exists after write error: %v", statErr) + } +} + func TestHandleDescEditDoneRemovesTempFileOnEditorError(t *testing.T) { tmp := t.TempDir() tempFile := filepath.Join(tmp, "desc.txt") @@ -228,6 +249,22 @@ func TestHandleDescEditDoneRemovesTempFileOnEditorError(t *testing.T) { } } +type failingDescriptionTempFile struct { + path string +} + +func (f *failingDescriptionTempFile) Name() string { + return f.path +} + +func (f *failingDescriptionTempFile) WriteString(string) (int, error) { + return 0, fmt.Errorf("write failed") +} + +func (f *failingDescriptionTempFile) Close() error { + return nil +} + func TestHandleFilterModeReportsReloadError(t *testing.T) { tmp := t.TempDir() taskPath := filepath.Join(tmp, "task") |
