|
|
|
@ -20,6 +20,8 @@ import (
|
|
|
|
|
"runtime"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"inet.af/netaddr"
|
|
|
|
|
"tailscale.com/ipn"
|
|
|
|
@ -338,6 +340,52 @@ This is my Tailscale device. Your device is %v.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
copied int64
|
|
|
|
|
lastNotify time.Time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *incomingFile) Write(p []byte) (n int, err error) {
|
|
|
|
|
n, err = f.w.Write(p)
|
|
|
|
|
|
|
|
|
|
b := f.ph.ps.b
|
|
|
|
|
var needNotify bool
|
|
|
|
|
defer func() {
|
|
|
|
|
if needNotify {
|
|
|
|
|
b.sendFileNotify()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
if n > 0 {
|
|
|
|
|
f.mu.Lock()
|
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
f.copied += int64(n)
|
|
|
|
|
now := time.Now()
|
|
|
|
|
if f.lastNotify.IsZero() || now.Sub(f.lastNotify) > time.Second {
|
|
|
|
|
f.lastNotify = now
|
|
|
|
|
needNotify = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return n, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *incomingFile) PartialFile() ipn.PartialFile {
|
|
|
|
|
f.mu.Lock()
|
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
return ipn.PartialFile{
|
|
|
|
|
Name: f.name,
|
|
|
|
|
Started: f.started,
|
|
|
|
|
DeclaredSize: f.size,
|
|
|
|
|
Received: f.copied,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if !h.isSelf {
|
|
|
|
|
http.Error(w, "not owner", http.StatusForbidden)
|
|
|
|
@ -369,12 +417,25 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
os.Remove(dstFile)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
n, err := io.Copy(f, r.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
f.Close()
|
|
|
|
|
h.logf("put Copy error: %v", err)
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
var finalSize int64
|
|
|
|
|
if r.ContentLength != 0 {
|
|
|
|
|
inFile := &incomingFile{
|
|
|
|
|
name: baseName,
|
|
|
|
|
started: time.Now(),
|
|
|
|
|
size: r.ContentLength,
|
|
|
|
|
w: f,
|
|
|
|
|
ph: h,
|
|
|
|
|
}
|
|
|
|
|
h.ps.b.registerIncomingFile(inFile, true)
|
|
|
|
|
defer h.ps.b.registerIncomingFile(inFile, false)
|
|
|
|
|
n, err := io.Copy(inFile, r.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
f.Close()
|
|
|
|
|
h.logf("put Copy error: %v", err)
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
finalSize = n
|
|
|
|
|
}
|
|
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
|
h.logf("put Close error: %v", err)
|
|
|
|
@ -382,14 +443,14 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.logf("put of %s from %v/%v", baseName, approxSize(n), h.remoteAddr.IP, h.peerNode.ComputedName)
|
|
|
|
|
h.logf("put of %s from %v/%v", baseName, approxSize(finalSize), h.remoteAddr.IP, h.peerNode.ComputedName)
|
|
|
|
|
|
|
|
|
|
// TODO: set modtime
|
|
|
|
|
// TODO: some real response
|
|
|
|
|
success = true
|
|
|
|
|
io.WriteString(w, "{}\n")
|
|
|
|
|
h.ps.knownEmpty.Set(false)
|
|
|
|
|
h.ps.b.send(ipn.Notify{}) // it will set FilesWaiting
|
|
|
|
|
h.ps.b.sendFileNotify()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func approxSize(n int64) string {
|
|
|
|
|