Skip to content

Commit

Permalink
ipn/localapi: add support for multipart POST to file-put
Browse files Browse the repository at this point in the history
This allows sending multiple files via Taildrop in one request.
Progress is tracked via ipn.Notify.

Updates tailscale/corp#18202

Signed-off-by: Percy Wegmann <percy@tailscale.com>
  • Loading branch information
oxtoacart committed Mar 27, 2024
1 parent 0d8cd16 commit bed818a
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 17 deletions.
24 changes: 21 additions & 3 deletions ipn/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ const (
NotifyInitialPrefs // if set, the first Notify message (sent immediately) will contain the current Prefs
NotifyInitialNetMap // if set, the first Notify message (sent immediately) will contain the current NetMap

NotifyNoPrivateKeys // if set, private keys that would normally be sent in updates are zeroed out
NotifyInitialTailFSShares // if set, the first Notify message (sent immediately) will contain the current TailFS Shares
NotifyNoPrivateKeys // if set, private keys that would normally be sent in updates are zeroed out
NotifyInitialTailFSShares // if set, the first Notify message (sent immediately) will contain the current TailFS Shares
NotifyInitialOutgoingFiles // if set, the first Notify message (sent immediately) will contain the current Taildrop OutgoingFiles
)

// Notify is a communication from a backend (e.g. tailscaled) to a frontend
Expand Down Expand Up @@ -114,6 +115,11 @@ type Notify struct {
// Deprecated: use LocalClient.AwaitWaitingFiles instead.
IncomingFiles []PartialFile `json:",omitempty"`

// OutgoingFiles, if non-nil, tracks which files are in the process of
// being sent via TailDrop, including files that finished, whether
// successful or failed. This slice is sorted by Started time, then Name.
OutgoingFiles []*OutgoingFile `json:",omitempty"`

// LocalTCPPort, if non-nil, informs the UI frontend which
// (non-zero) localhost TCP port it's listening on.
// This is currently only used by Tailscale when run in the
Expand Down Expand Up @@ -175,7 +181,7 @@ func (n Notify) String() string {
return s[0:len(s)-1] + "}"
}

// PartialFile represents an in-progress file transfer.
// PartialFile represents an in-progress incoming file transfer.
type PartialFile struct {
Name string // e.g. "foo.jpg"
Started time.Time // time transfer started
Expand All @@ -194,6 +200,18 @@ type PartialFile struct {
Done bool `json:",omitempty"`
}

// OutgoingFile represents an in-progress outgoing file transfer.
type OutgoingFile struct {
ID string `json:"-"` // unique identifier for this transfer (a type 4 UUID)
PeerID tailcfg.StableNodeID // identifier for the peer to which this is being transferred
Name string `json:",omitempty"` // e.g. "foo.jpg"
Started time.Time // time transfer started
DeclaredSize int64 // or -1 if unknown
Sent int64 // bytes copied thus far
Finished bool // indicates whether or not the transfer finished
Succeeded bool // for a finished transfer, indicates whether or not it was successful
}

// StateKey is an opaque identifier for a set of LocalBackend state
// (preferences, private keys, etc.). It is also used as a key for
// the various LoginProfiles that the instance may be signed into.
Expand Down
3 changes: 3 additions & 0 deletions ipn/ipnlocal/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ type LocalBackend struct {
// lastNotifiedTailFSShares keeps track of the last set of shares that we
// notified about.
lastNotifiedTailFSShares atomic.Pointer[views.SliceView[*tailfs.Share, tailfs.ShareView]]

// outgoingFiles keeps track of Taildrop outgoing files
outgoingFiles map[string]*ipn.OutgoingFile
}

type updateStatus struct {
Expand Down
34 changes: 34 additions & 0 deletions ipn/ipnlocal/taildrop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

package ipnlocal

import (
"slices"
"strings"

"tailscale.com/ipn"
)

func (b *LocalBackend) UpdateOutgoingFiles(updates map[string]ipn.OutgoingFile) {
b.mu.Lock()
if b.outgoingFiles == nil {
b.outgoingFiles = make(map[string]*ipn.OutgoingFile, len(updates))
}
for id, file := range updates {
b.outgoingFiles[id] = &file
}
outgoingFiles := make([]*ipn.OutgoingFile, 0, len(b.outgoingFiles))
for _, file := range b.outgoingFiles {
outgoingFiles = append(outgoingFiles, file)
}
b.mu.Unlock()
slices.SortFunc(outgoingFiles, func(a, b *ipn.OutgoingFile) int {
t := a.Started.Compare(b.Started)
if t != 0 {
return t
}
return strings.Compare(a.Name, b.Name)
})
b.send(ipn.Notify{OutgoingFiles: outgoingFiles})
}

0 comments on commit bed818a

Please sign in to comment.