mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-08 09:07:44 +00:00
ipn{,/ipnlocal}, client/tailscale: move Taildrop recv notifications to LocalAPI HTTP method
Updates #6417 Change-Id: Iec544c477a0e5e9f1c6bf23555afec06255e2e22 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
f053f16460
commit
0f7da5c7dc
@ -426,8 +426,20 @@ func (lc *LocalClient) IDToken(ctx context.Context, aud string) (*tailcfg.TokenR
|
|||||||
return decodeJSON[*tailcfg.TokenResponse](body)
|
return decodeJSON[*tailcfg.TokenResponse](body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WaitingFiles returns the list of received Taildrop files that have been
|
||||||
|
// received by the Tailscale daemon in its staging/cache directory but not yet
|
||||||
|
// transferred by the user's CLI or GUI client and written to a user's home
|
||||||
|
// directory somewhere.
|
||||||
func (lc *LocalClient) WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) {
|
func (lc *LocalClient) WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) {
|
||||||
body, err := lc.get200(ctx, "/localapi/v0/files/")
|
return lc.AwaitWaitingFiles(ctx, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AwaitWaitingFiles is like WaitingFiles but takes a duration to await for an answer.
|
||||||
|
// If the duration is 0, it will return immediately. The duration is respected at second
|
||||||
|
// granularity only. If no files are available, it returns (nil, nil).
|
||||||
|
func (lc *LocalClient) AwaitWaitingFiles(ctx context.Context, d time.Duration) ([]apitype.WaitingFile, error) {
|
||||||
|
path := "/localapi/v0/files/?waitsec=" + fmt.Sprint(int(d.Seconds()))
|
||||||
|
body, err := lc.get200(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/ipn"
|
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/util/quarantine"
|
"tailscale.com/util/quarantine"
|
||||||
@ -529,30 +528,16 @@ func wipeInbox(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func waitForFile(ctx context.Context) error {
|
func waitForFile(ctx context.Context) error {
|
||||||
c, bc, pumpCtx, cancel := connect(ctx)
|
for {
|
||||||
defer cancel()
|
ff, err := localClient.AwaitWaitingFiles(ctx, time.Hour)
|
||||||
fileWaiting := make(chan bool, 1)
|
if len(ff) > 0 {
|
||||||
notifyError := make(chan error, 1)
|
|
||||||
bc.SetNotifyCallback(func(n ipn.Notify) {
|
|
||||||
if n.ErrMessage != nil {
|
|
||||||
notifyError <- fmt.Errorf("Notify.ErrMessage: %v", *n.ErrMessage)
|
|
||||||
}
|
|
||||||
if n.FilesWaiting != nil {
|
|
||||||
select {
|
|
||||||
case fileWaiting <- true:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
go pump(pumpCtx, bc, c)
|
|
||||||
select {
|
|
||||||
case <-fileWaiting:
|
|
||||||
return nil
|
return nil
|
||||||
case <-pumpCtx.Done():
|
}
|
||||||
return pumpCtx.Err()
|
if err := ctx.Err(); err != nil {
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case err := <-notifyError:
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,8 @@ type Notify struct {
|
|||||||
// FilesWaiting if non-nil means that files are buffered in
|
// FilesWaiting if non-nil means that files are buffered in
|
||||||
// the Tailscale daemon and ready for local transfer to the
|
// the Tailscale daemon and ready for local transfer to the
|
||||||
// user's preferred storage location.
|
// user's preferred storage location.
|
||||||
|
//
|
||||||
|
// Deprecated: use LocalClient.AwaitWaitingFiles instead.
|
||||||
FilesWaiting *empty.Message `json:",omitempty"`
|
FilesWaiting *empty.Message `json:",omitempty"`
|
||||||
|
|
||||||
// IncomingFiles, if non-nil, specifies which files are in the
|
// IncomingFiles, if non-nil, specifies which files are in the
|
||||||
@ -83,6 +85,8 @@ type Notify struct {
|
|||||||
// Notify should not update the state of file transfers. A non-nil
|
// Notify should not update the state of file transfers. A non-nil
|
||||||
// but empty IncomingFiles means that no files are in the middle
|
// but empty IncomingFiles means that no files are in the middle
|
||||||
// of being transferred.
|
// of being transferred.
|
||||||
|
//
|
||||||
|
// Deprecated: use LocalClient.AwaitWaitingFiles instead.
|
||||||
IncomingFiles []PartialFile `json:",omitempty"`
|
IncomingFiles []PartialFile `json:",omitempty"`
|
||||||
|
|
||||||
// LocalTCPPort, if non-nil, informs the UI frontend which
|
// LocalTCPPort, if non-nil, informs the UI frontend which
|
||||||
|
@ -180,6 +180,7 @@ type LocalBackend struct {
|
|||||||
peerAPIListeners []*peerAPIListener
|
peerAPIListeners []*peerAPIListener
|
||||||
loginFlags controlclient.LoginFlags
|
loginFlags controlclient.LoginFlags
|
||||||
incomingFiles map[*incomingFile]bool
|
incomingFiles map[*incomingFile]bool
|
||||||
|
fileWaiters map[*mapSetHandle]context.CancelFunc // handle => func to call on file received
|
||||||
lastStatusTime time.Time // status.AsOf value of the last processed status update
|
lastStatusTime time.Time // status.AsOf value of the last processed status update
|
||||||
// directFileRoot, if non-empty, means to write received files
|
// directFileRoot, if non-empty, means to write received files
|
||||||
// directly to this directory, without staging them in an
|
// directly to this directory, without staging them in an
|
||||||
@ -1709,6 +1710,9 @@ func (b *LocalBackend) sendFileNotify() {
|
|||||||
var n ipn.Notify
|
var n ipn.Notify
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
|
for _, wakeWaiter := range b.fileWaiters {
|
||||||
|
wakeWaiter()
|
||||||
|
}
|
||||||
notifyFunc := b.notify
|
notifyFunc := b.notify
|
||||||
apiSrv := b.peerAPIServer
|
apiSrv := b.peerAPIServer
|
||||||
if notifyFunc == nil || apiSrv == nil {
|
if notifyFunc == nil || apiSrv == nil {
|
||||||
@ -3579,6 +3583,20 @@ func (b *LocalBackend) TestOnlyPublicKeys() (machineKey key.MachinePublic, nodeK
|
|||||||
return mk, nk
|
return mk, nk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mapSetHandle is a minimal (but non-zero) value whose address serves as a map
|
||||||
|
// key for sets of non-comparable values that can't be map keys themselves.
|
||||||
|
type mapSetHandle byte
|
||||||
|
|
||||||
|
func (b *LocalBackend) setFileWaiter(handle *mapSetHandle, wakeWaiter context.CancelFunc) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if wakeWaiter == nil {
|
||||||
|
delete(b.fileWaiters, handle)
|
||||||
|
} else {
|
||||||
|
mak.Set(&b.fileWaiters, handle, wakeWaiter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) WaitingFiles() ([]apitype.WaitingFile, error) {
|
func (b *LocalBackend) WaitingFiles() ([]apitype.WaitingFile, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
apiSrv := b.peerAPIServer
|
apiSrv := b.peerAPIServer
|
||||||
@ -3586,6 +3604,42 @@ func (b *LocalBackend) WaitingFiles() ([]apitype.WaitingFile, error) {
|
|||||||
return apiSrv.WaitingFiles()
|
return apiSrv.WaitingFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AwaitWaitingFiles is like WaitingFiles but blocks while ctx is not done,
|
||||||
|
// waiting for any files to be available.
|
||||||
|
//
|
||||||
|
// On return, exactly one of the results will be non-empty or non-nil,
|
||||||
|
// respectively.
|
||||||
|
func (b *LocalBackend) AwaitWaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) {
|
||||||
|
if ff, err := b.WaitingFiles(); err != nil || len(ff) > 0 {
|
||||||
|
return ff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
gotFile, gotFileCancel := context.WithCancel(context.Background())
|
||||||
|
defer gotFileCancel()
|
||||||
|
|
||||||
|
handle := new(mapSetHandle)
|
||||||
|
b.setFileWaiter(handle, gotFileCancel)
|
||||||
|
defer b.setFileWaiter(handle, nil)
|
||||||
|
|
||||||
|
// Now that we've registered ourselves, check again, in case
|
||||||
|
// of race. Otherwise there's a small window where we could
|
||||||
|
// miss a file arrival and wait forever.
|
||||||
|
if ff, err := b.WaitingFiles(); err != nil || len(ff) > 0 {
|
||||||
|
return ff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-gotFile.Done():
|
||||||
|
if ff, err := b.WaitingFiles(); err != nil || len(ff) > 0 {
|
||||||
|
return ff, err
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) DeleteFile(name string) error {
|
func (b *LocalBackend) DeleteFile(name string) error {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
apiSrv := b.peerAPIServer
|
apiSrv := b.peerAPIServer
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -680,8 +681,20 @@ func (h *Handler) serveFiles(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "want GET to list files", 400)
|
http.Error(w, "want GET to list files", 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wfs, err := h.b.WaitingFiles()
|
ctx := r.Context()
|
||||||
|
if s := r.FormValue("waitsec"); s != "" && s != "0" {
|
||||||
|
d, err := strconv.Atoi(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
http.Error(w, "invalid waitsec", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deadline := time.Now().Add(time.Duration(d) * time.Second)
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithDeadline(ctx, deadline)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
wfs, err := h.b.AwaitWaitingFiles(ctx)
|
||||||
|
if err != nil && ctx.Err() == nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user