@@ -2,28 +2,33 @@ package imapclient
22
33import (
44 "fmt"
5+ "sync/atomic"
6+ "time"
57)
68
9+ const idleRestartInterval = 28 * time .Minute
10+
711// Idle sends an IDLE command.
812//
913// Unlike other commands, this method blocks until the server acknowledges it.
1014// On success, the IDLE command is running and other commands cannot be sent.
1115// The caller must invoke IdleCommand.Close to stop IDLE and unblock the
1216// client.
1317//
14- // This command requires support for IMAP4rev2 or the IDLE extension.
18+ // This command requires support for IMAP4rev2 or the IDLE extension. The IDLE
19+ // command is restarted automatically to avoid getting disconnected due to
20+ // inactivity timeouts.
1521func (c * Client ) Idle () (* IdleCommand , error ) {
16- cmd := & IdleCommand {}
17- contReq := c .registerContReq (cmd )
18- cmd .enc = c .beginCommand ("IDLE" , cmd )
19- cmd .enc .flush ()
20-
21- _ , err := contReq .Wait ()
22+ child , err := c .idle ()
2223 if err != nil {
23- cmd .enc .end ()
2424 return nil , err
2525 }
2626
27+ cmd := & IdleCommand {
28+ stop : make (chan struct {}),
29+ done : make (chan struct {}),
30+ }
31+ go cmd .run (c , child )
2732 return cmd , nil
2833}
2934
@@ -34,6 +39,90 @@ func (c *Client) Idle() (*IdleCommand, error) {
3439//
3540// Close must be called to stop the IDLE command.
3641type IdleCommand struct {
42+ stopped atomic.Bool
43+ stop chan struct {}
44+ done chan struct {}
45+
46+ err error
47+ lastChild * idleCommand
48+ }
49+
50+ func (cmd * IdleCommand ) run (c * Client , child * idleCommand ) {
51+ defer close (cmd .done )
52+
53+ timer := time .NewTimer (idleRestartInterval )
54+ defer timer .Stop ()
55+
56+ defer func () {
57+ if child != nil {
58+ if err := child .Close (); err != nil && cmd .err == nil {
59+ cmd .err = err
60+ }
61+ }
62+ }()
63+
64+ for {
65+ select {
66+ case <- timer .C :
67+ timer .Reset (idleRestartInterval )
68+
69+ if cmd .err = child .Close (); cmd .err != nil {
70+ return
71+ }
72+ if child , cmd .err = c .idle (); cmd .err != nil {
73+ return
74+ }
75+ case <- cmd .stop :
76+ cmd .lastChild = child
77+ return
78+ }
79+ }
80+ }
81+
82+ // Close stops the IDLE command.
83+ //
84+ // This method blocks until the command to stop IDLE is written, but doesn't
85+ // wait for the server to respond. Callers can use Wait for this purpose.
86+ func (cmd * IdleCommand ) Close () error {
87+ if cmd .stopped .Swap (true ) {
88+ return fmt .Errorf ("imapclient: IDLE already closed" )
89+ }
90+ close (cmd .stop )
91+ <- cmd .done
92+ return cmd .err
93+ }
94+
95+ // Wait blocks until the IDLE command has completed.
96+ //
97+ // Wait can only be called after Close.
98+ func (cmd * IdleCommand ) Wait () error {
99+ if ! cmd .stopped .Load () {
100+ return fmt .Errorf ("imapclient: IdleCommand.Close must be called before Wait" )
101+ }
102+ <- cmd .done
103+ if cmd .err != nil {
104+ return cmd .err
105+ }
106+ return cmd .lastChild .Wait ()
107+ }
108+
109+ func (c * Client ) idle () (* idleCommand , error ) {
110+ cmd := & idleCommand {}
111+ contReq := c .registerContReq (cmd )
112+ cmd .enc = c .beginCommand ("IDLE" , cmd )
113+ cmd .enc .flush ()
114+
115+ _ , err := contReq .Wait ()
116+ if err != nil {
117+ cmd .enc .end ()
118+ return nil , err
119+ }
120+
121+ return cmd , nil
122+ }
123+
124+ // idleCommand represents a singular IDLE command, without the restart logic.
125+ type idleCommand struct {
37126 cmd
38127 enc * commandEncoder
39128}
@@ -42,7 +131,7 @@ type IdleCommand struct {
42131//
43132// This method blocks until the command to stop IDLE is written, but doesn't
44133// wait for the server to respond. Callers can use Wait for this purpose.
45- func (cmd * IdleCommand ) Close () error {
134+ func (cmd * idleCommand ) Close () error {
46135 if cmd .err != nil {
47136 return cmd .err
48137 }
@@ -62,9 +151,9 @@ func (cmd *IdleCommand) Close() error {
62151// Wait blocks until the IDLE command has completed.
63152//
64153// Wait can only be called after Close.
65- func (cmd * IdleCommand ) Wait () error {
154+ func (cmd * idleCommand ) Wait () error {
66155 if cmd .enc != nil {
67- return fmt . Errorf ("imapclient: IdleCommand .Close must be called before Wait" )
156+ panic ("imapclient: idleCommand .Close must be called before Wait" )
68157 }
69158 return cmd .cmd .Wait ()
70159}
0 commit comments