Skip to content

Commit e9e15d9

Browse files
committed
mark as read in get&export flow
1 parent f11ace6 commit e9e15d9

6 files changed

Lines changed: 119 additions & 0 deletions

File tree

cmd/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"strings"
77

8+
"github.com/hardhacker/podwise-cli/internal/async"
89
"github.com/hardhacker/podwise-cli/internal/update"
910
"github.com/spf13/cobra"
1011
)
@@ -31,10 +32,12 @@ func Execute(version, commit, date string) {
3132
updateCh := maybeStartUpdateCheck(version)
3233

3334
if err := rootCmd.Execute(); err != nil {
35+
async.Wait() // Wait for background tasks before exit
3436
os.Exit(1)
3537
}
3638

3739
printUpdateNotice(updateCh)
40+
async.Wait() // Wait for background tasks before exit
3841
}
3942

4043
// maybeStartUpdateCheck starts an async update check and returns a channel that

internal/async/async.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package async
2+
3+
import "sync"
4+
5+
// Manager manages background goroutines and ensures they complete before exit.
6+
type Manager struct {
7+
wg sync.WaitGroup
8+
}
9+
10+
// defaultManager is the global instance used by package-level functions.
11+
var defaultManager = &Manager{}
12+
13+
// Go spawns a goroutine and tracks it for graceful shutdown.
14+
// The function f is executed in a new goroutine.
15+
func (m *Manager) Go(f func()) {
16+
m.wg.Add(1)
17+
go func() {
18+
defer m.wg.Done()
19+
f()
20+
}()
21+
}
22+
23+
// Wait blocks until all tracked goroutines have completed.
24+
func (m *Manager) Wait() {
25+
m.wg.Wait()
26+
}
27+
28+
// Go spawns a goroutine using the default manager.
29+
func Go(f func()) {
30+
defaultManager.Go(f)
31+
}
32+
33+
// Wait blocks until all goroutines spawned via the default manager have completed.
34+
func Wait() {
35+
defaultManager.Wait()
36+
}

internal/episode/export.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

77
"github.com/hardhacker/podwise-cli/internal/api"
8+
"github.com/hardhacker/podwise-cli/internal/async"
89
)
910

1011
// NotionExportOptions holds parameters for exporting to Notion.
@@ -73,6 +74,11 @@ func ExportToNotion(ctx context.Context, client *api.Client, seq int, opts Notio
7374
return nil, formatNotionError(err)
7475
}
7576

77+
// Mark episode as read in background
78+
async.Go(func() {
79+
_ = MarkAsRead(context.Background(), client, seq)
80+
})
81+
7682
return &resp.Result, nil
7783
}
7884

@@ -170,6 +176,11 @@ func ExportToReadwise(ctx context.Context, client *api.Client, seq int, opts Rea
170176
return nil, formatReadwiseError(err)
171177
}
172178

179+
// Mark episode as read in background
180+
async.Go(func() {
181+
_ = MarkAsRead(context.Background(), client, seq)
182+
})
183+
173184
return &resp.Result, nil
174185
}
175186

internal/episode/mark.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package episode
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hardhacker/podwise-cli/internal/api"
8+
)
9+
10+
// MarkResponse represents the API response for mark read/unread operations.
11+
type MarkResponse struct {
12+
Success bool `json:"success"`
13+
}
14+
15+
// MarkAsRead marks an episode as read for the authenticated user.
16+
// The operation is idempotent - if the episode is already marked as read,
17+
// the request succeeds silently.
18+
//
19+
// Returns an error if:
20+
// - The episode does not exist (404 not_found)
21+
// - The API request fails for other reasons
22+
func MarkAsRead(ctx context.Context, client *api.Client, seq int) error {
23+
path := fmt.Sprintf("/open/v1/episodes/%d/read", seq)
24+
25+
var resp MarkResponse
26+
if err := client.Post(ctx, path, nil, &resp); err != nil {
27+
return fmt.Errorf("mark episode %d as read: %w", seq, err)
28+
}
29+
30+
if !resp.Success {
31+
return fmt.Errorf("mark episode %d as read: operation failed", seq)
32+
}
33+
34+
return nil
35+
}
36+
37+
// MarkAsUnread marks an episode as unread for the authenticated user.
38+
// The operation is idempotent - if the episode is already unread or has no
39+
// read record, the request succeeds silently.
40+
//
41+
// Returns an error if:
42+
// - The episode does not exist (404 not_found)
43+
// - The API request fails for other reasons
44+
func MarkAsUnread(ctx context.Context, client *api.Client, seq int) error {
45+
path := fmt.Sprintf("/open/v1/episodes/%d/unread", seq)
46+
47+
var resp MarkResponse
48+
if err := client.Post(ctx, path, nil, &resp); err != nil {
49+
return fmt.Errorf("mark episode %d as unread: %w", seq, err)
50+
}
51+
52+
if !resp.Success {
53+
return fmt.Errorf("mark episode %d as unread: operation failed", seq)
54+
}
55+
56+
return nil
57+
}

internal/episode/summary.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/hardhacker/podwise-cli/internal/api"
10+
"github.com/hardhacker/podwise-cli/internal/async"
1011
"github.com/hardhacker/podwise-cli/internal/cache"
1112
)
1213

@@ -197,6 +198,11 @@ func FetchSummary(ctx context.Context, client *api.Client, seq int, forceRefresh
197198
fmt.Printf("warning: could not write cache: %v\n", err)
198199
}
199200

201+
// Mark episode as read in background
202+
async.Go(func() {
203+
_ = MarkAsRead(context.Background(), client, seq)
204+
})
205+
200206
return &resp.Result, nil
201207
}
202208

internal/episode/transcript.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
"github.com/hardhacker/podwise-cli/internal/api"
12+
"github.com/hardhacker/podwise-cli/internal/async"
1213
"github.com/hardhacker/podwise-cli/internal/cache"
1314
)
1415

@@ -65,6 +66,11 @@ func FetchTranscripts(ctx context.Context, client *api.Client, seq int, forceRef
6566
fmt.Printf("warning: could not write cache: %v\n", err)
6667
}
6768

69+
// Mark episode as read in background
70+
async.Go(func() {
71+
_ = MarkAsRead(context.Background(), client, seq)
72+
})
73+
6874
return resp.Result, nil
6975
}
7076

0 commit comments

Comments
 (0)