From 7050b288853cd5f7894d89cf6f90abfbee4717d5 Mon Sep 17 00:00:00 2001 From: Rhea Ghosh Date: Sat, 21 Oct 2023 18:28:07 -0500 Subject: [PATCH] taildrop macos testing --- ipn/backend.go | 22 +---------- ipn/ipnlocal/local.go | 21 +++++++++- taildrop/delete.go | 2 +- taildrop/send.go | 90 ++++++++++++++++++++++++++++++++----------- taildrop/taildrop.go | 26 ++++++------- 5 files changed, 104 insertions(+), 57 deletions(-) diff --git a/ipn/backend.go b/ipn/backend.go index ad5dbd4bf..1cabc5788 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -6,10 +6,10 @@ package ipn import ( "fmt" "strings" - "time" "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" + "tailscale.com/taildrop" "tailscale.com/types/empty" "tailscale.com/types/key" "tailscale.com/types/netmap" @@ -109,7 +109,7 @@ type Notify struct { // of being transferred. // // Deprecated: use LocalClient.AwaitWaitingFiles instead. - IncomingFiles []PartialFile `json:",omitempty"` + IncomingFiles []taildrop.PartialFile `json:",omitempty"` // LocalTCPPort, if non-nil, informs the UI frontend which // (non-zero) localhost TCP port it's listening on. @@ -164,24 +164,6 @@ func (n Notify) String() string { return s[0:len(s)-1] + "}" } -// PartialFile represents an in-progress file transfer. -type PartialFile struct { - Name string // e.g. "foo.jpg" - Started time.Time // time transfer started - DeclaredSize int64 // or -1 if unknown - Received int64 // bytes copied thus far - - // PartialPath is set non-empty in "direct" file mode to the - // in-progress '*.partial' file's path when the peerapi isn't - // being used; see LocalBackend.SetDirectFileRoot. - PartialPath string `json:",omitempty"` - - // Done is set in "direct" mode when the partial file has been - // closed and is ready for the caller to rename away the - // ".partial" suffix. - Done bool `json:",omitempty"` -} - // 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. diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index c4ed545a7..1357e7304 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -240,6 +240,7 @@ type LocalBackend struct { peerAPIServer *peerAPIServer // or nil peerAPIListeners []*peerAPIListener loginFlags controlclient.LoginFlags + incomingFiles map[*taildrop.IncomingFile]bool fileWaiters set.HandleSet[context.CancelFunc] // of wake-up funcs notifyWatchers set.HandleSet[*watchSession] lastStatusTime time.Time // status.AsOf value of the last processed status update @@ -2278,7 +2279,12 @@ func (b *LocalBackend) sendFileNotify() { // Make sure we always set n.IncomingFiles non-nil so it gets encoded // in JSON to clients. They distinguish between empty and non-nil // to know whether a Notify should be able about files. - n.IncomingFiles = apiSrv.taildrop.IncomingFiles() + + //// n.IncomingFiles = apiSrv.taildrop.IncomingFiles() + n.IncomingFiles = make([]taildrop.PartialFile, 0) + for f := range b.incomingFiles { + n.IncomingFiles = append(n.IncomingFiles, f.PartialFile()) + } b.mu.Unlock() sort.Slice(n.IncomingFiles, func(i, j int) bool { @@ -4657,6 +4663,19 @@ func (b *LocalBackend) SetDNS(ctx context.Context, name, value string) error { return cc.SetDNS(ctx, req) } +func (b *LocalBackend) registerIncomingFile(inf *taildrop.IncomingFile, active bool) { + b.mu.Lock() + defer b.mu.Unlock() + if b.incomingFiles == nil { + b.incomingFiles = make(map[*taildrop.IncomingFile]bool) + } + if active { + b.incomingFiles[inf] = true + } else { + delete(b.incomingFiles, inf) + } +} + func peerAPIPorts(peer tailcfg.NodeView) (p4, p6 uint16) { svcs := peer.Hostinfo().Services() for i := range svcs.LenIter() { diff --git a/taildrop/delete.go b/taildrop/delete.go index 12b95c020..bf55d3309 100644 --- a/taildrop/delete.go +++ b/taildrop/delete.go @@ -70,7 +70,7 @@ func (d *fileDeleter) Init(m *Manager, eventHook func(string)) { nameID := strings.TrimSuffix(de.Name(), partialSuffix) if i := strings.LastIndexByte(nameID, '.'); i > 0 { key := incomingFileKey{ClientID(nameID[i+len("."):]), nameID[:i]} - m.incomingFiles.LoadFunc(key, func(_ *incomingFile, loaded bool) { + m.incomingFiles.LoadFunc(key, func(_ *IncomingFile, loaded bool) { if !loaded { d.Insert(de.Name()) } diff --git a/taildrop/send.go b/taildrop/send.go index 42c223737..8fe6786a7 100644 --- a/taildrop/send.go +++ b/taildrop/send.go @@ -22,23 +22,69 @@ type incomingFileKey struct { name string // e.g., "foo.jpeg" } -type incomingFile struct { +type IncomingFile struct { clock tstime.DefaultClock - started time.Time - size int64 // or -1 if unknown; never 0 - w io.Writer // underlying writer + Name string // "foo.jpg" + Started time.Time + Size int64 // or -1 if unknown; never 0 + W io.Writer // underlying writer sendFileNotify func() // called when done - partialPath string // non-empty in direct mode + PartialPath string // non-empty in direct mode - mu sync.Mutex - copied int64 - done bool + Mu sync.Mutex + Copied int64 + Done bool lastNotify time.Time } -func (f *incomingFile) Write(p []byte) (n int, err error) { - n, err = f.w.Write(p) +// type incomingFile struct { +// name string // "foo.jpg" +// started time.Time +// size int64 // or -1 if unknown; never 0 +// w io.Writer // underlying writer +// ph *peerAPIHandler +// partialPath string // non-empty in direct mode + +// mu sync.Mutex +// copied int64 +// done bool +// lastNotify time.Time +// } + +func (f *IncomingFile) PartialFile() PartialFile { + f.Mu.Lock() + defer f.Mu.Unlock() + return PartialFile{ + Name: f.Name, + Started: f.Started, + DeclaredSize: f.Size, + Received: f.Copied, + PartialPath: f.PartialPath, + Done: f.Done, + } +} + +// PartialFile represents an in-progress file transfer. +type PartialFile struct { + Name string // e.g. "foo.jpg" + Started time.Time // time transfer started + DeclaredSize int64 // or -1 if unknown + Received int64 // bytes copied thus far + + // PartialPath is set non-empty in "direct" file mode to the + // in-progress '*.partial' file's path when the peerapi isn't + // being used; see LocalBackend.SetDirectFileRoot. + PartialPath string `json:",omitempty"` + + // Done is set in "direct" mode when the partial file has been + // closed and is ready for the caller to rename away the + // ".partial" suffix. + Done bool `json:",omitempty"` +} + +func (f *IncomingFile) Write(p []byte) (n int, err error) { + n, err = f.W.Write(p) var needNotify bool defer func() { @@ -47,9 +93,9 @@ func (f *incomingFile) Write(p []byte) (n int, err error) { } }() if n > 0 { - f.mu.Lock() - defer f.mu.Unlock() - f.copied += int64(n) + f.Mu.Lock() + defer f.Mu.Unlock() + f.Copied += int64(n) now := f.clock.Now() if f.lastNotify.IsZero() || now.Sub(f.lastNotify) > time.Second { f.lastNotify = now @@ -101,15 +147,15 @@ func (m *Manager) PutFile(id ClientID, baseName string, r io.Reader, offset, len // Check whether there is an in-progress transfer for the file. partialPath := dstPath + id.partialSuffix() inFileKey := incomingFileKey{id, baseName} - inFile, loaded := m.incomingFiles.LoadOrInit(inFileKey, func() *incomingFile { - inFile := &incomingFile{ + inFile, loaded := m.incomingFiles.LoadOrInit(inFileKey, func() *IncomingFile { + inFile := &IncomingFile{ clock: m.opts.Clock, - started: m.opts.Clock.Now(), - size: length, + Started: m.opts.Clock.Now(), + Size: length, sendFileNotify: m.opts.SendFileNotify, } if m.opts.DirectFileMode { - inFile.partialPath = partialPath + inFile.PartialPath = partialPath } return inFile }) @@ -134,7 +180,7 @@ func (m *Manager) PutFile(id ClientID, baseName string, r io.Reader, offset, len m.deleter.Insert(filepath.Base(partialPath)) // mark partial file for eventual deletion } }() - inFile.w = f + inFile.W = f // A positive offset implies that we are resuming an existing file. // Seek to the appropriate offset and truncate the file. @@ -170,9 +216,9 @@ func (m *Manager) PutFile(id ClientID, baseName string, r io.Reader, offset, len // Return early for avoidPartialRename since users of AvoidFinalRename // are depending on the exact naming of partial files. if avoidPartialRename { - inFile.mu.Lock() - inFile.done = true - inFile.mu.Unlock() + inFile.Mu.Lock() + inFile.Done = true + inFile.Mu.Unlock() m.totalReceived.Add(1) m.opts.SendFileNotify() return fileLength, nil diff --git a/taildrop/taildrop.go b/taildrop/taildrop.go index ceda9cd39..7d8daffaa 100644 --- a/taildrop/taildrop.go +++ b/taildrop/taildrop.go @@ -25,7 +25,6 @@ import ( "unicode" "unicode/utf8" - "tailscale.com/ipn" "tailscale.com/syncs" "tailscale.com/tstime" "tailscale.com/types/logger" @@ -110,7 +109,7 @@ type Manager struct { opts ManagerOptions // incomingFiles is a map of files actively being received. - incomingFiles syncs.Map[incomingFileKey, *incomingFile] + incomingFiles syncs.Map[incomingFileKey, *IncomingFile] // deleter managers asynchronous deletion of files. deleter fileDeleter @@ -229,21 +228,22 @@ func rangeDir(dir string, fn func(fs.DirEntry) bool) error { } // IncomingFiles returns a list of active incoming files. -func (m *Manager) IncomingFiles() []ipn.PartialFile { + +func (m *Manager) IncomingFiles() []PartialFile { // Make sure we always set n.IncomingFiles non-nil so it gets encoded // in JSON to clients. They distinguish between empty and non-nil // to know whether a Notify should be able about files. - files := make([]ipn.PartialFile, 0) - m.incomingFiles.Range(func(k incomingFileKey, f *incomingFile) bool { - f.mu.Lock() - defer f.mu.Unlock() - files = append(files, ipn.PartialFile{ + files := make([]PartialFile, 0) + m.incomingFiles.Range(func(k incomingFileKey, f *IncomingFile) bool { + f.Mu.Lock() + defer f.Mu.Unlock() + files = append(files, PartialFile{ Name: k.name, - Started: f.started, - DeclaredSize: f.size, - Received: f.copied, - PartialPath: f.partialPath, - Done: f.done, + Started: f.Started, + DeclaredSize: f.Size, + Received: f.Copied, + PartialPath: f.PartialPath, + Done: f.Done, }) return true })