Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 34 additions & 57 deletions cmd/receive.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package cmd

import (
"encoding/binary"
"context"
"fmt"
"io"
"net"
"os"
"path/filepath"
"time"

"github.com/neozmmv/blindspot/internal/transfer"
bstun "github.com/neozmmv/blindspot/internal/tun"
"github.com/neozmmv/blindspot/internal/utils"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -36,77 +34,56 @@ var ReceiveCmd = &cobra.Command{
return
}
} else {
home, err := os.UserHomeDir()
destDir, err = transfer.DownloadsDir()
if err != nil {
fmt.Println("Error finding home directory:", err)
return
}
destDir = filepath.Join(home, "Downloads")
if err := os.MkdirAll(destDir, 0755); err != nil {
fmt.Println("Error creating Downloads directory:", err)
fmt.Println(err)
return
}
}

myIP := bstun.VirtualIPv4(publicKey)
ln, err := net.Listen("tcp", myIP+transferPort)
recv, err := transfer.Listen(context.Background(), myIP)
if err != nil {
fmt.Printf("Could not listen on %s: %v\n", myIP+transferPort, err)
fmt.Println(err)
return
}
defer ln.Close()
defer recv.Close()

fmt.Printf("Waiting for file on %s...\n", myIP+transferPort)
fmt.Printf("Waiting for file on %s...\n", recv.Addr())

conn, err := ln.Accept()
if err != nil {
fmt.Println("Error accepting connection:", err)
return
}
defer conn.Close()

var nameLen uint16
if err := binary.Read(conn, binary.BigEndian, &nameLen); err != nil {
fmt.Println("Error reading filename length:", err)
return
}
nameBuf := make([]byte, nameLen)
if _, err := io.ReadFull(conn, nameBuf); err != nil {
fmt.Println("Error reading filename:", err)
return
}

var fileSize uint64
if err := binary.Read(conn, binary.BigEndian, &fileSize); err != nil {
fmt.Println("Error reading file size:", err)
return
}
// Render progress off a goroutine: the first snapshot prints the "Receiving…"
// header, later snapshots overwrite a single live line via carriage return.
prog := make(chan transfer.Progress, 1)
renderDone := make(chan struct{})
go func() {
var lr transfer.LineRenderer
first := true
for p := range prog {
if first {
first = false
lr.Seed(p)
fmt.Printf("Receiving %s (%d MB) from %s...\n", p.Name, p.Total/(1024*1024), p.PeerAddr)
continue
}
fmt.Print("\r" + lr.Line(p))
}
close(renderDone)
}()

filename := string(nameBuf)
destPath := filepath.Join(destDir, filename)
fmt.Printf("Receiving %s (%d MB) from %s...\n", filename, fileSize/(1024*1024), conn.RemoteAddr())
res, err := recv.Accept(context.Background(), destDir, prog)
close(prog)
<-renderDone

f, err := os.Create(destPath)
if err != nil {
fmt.Println("Error creating file:", err)
return
if enteredBody(err) {
fmt.Println()
}
defer f.Close()

pw := &progressWriter{w: f}
stop := startProgress(int64(fileSize), pw)
start := time.Now()
n, err := io.CopyN(pw, conn, int64(fileSize))
stop()
fmt.Println()
if err != nil {
fmt.Printf("Error receiving file (got %d/%d bytes): %v\n", n, fileSize, err)
fmt.Println(err)
return
}
elapsed := time.Since(start)
fmt.Printf("Saved to %s — %s in %s (avg %s/s)\n",
destPath, formatBytes(float64(n)), elapsed.Round(time.Millisecond),
formatBytes(float64(n)/elapsed.Seconds()))
res.Path, transfer.FormatBytes(float64(res.Bytes)), res.Elapsed.Round(time.Millisecond),
transfer.FormatBytes(float64(res.Bytes)/res.Elapsed.Seconds()))
},
}

Expand Down
93 changes: 44 additions & 49 deletions cmd/send.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
package cmd

import (
"encoding/binary"
"context"
"errors"
"fmt"
"io"
"net"
"os"
"path/filepath"
"time"

"github.com/neozmmv/blindspot/internal/transfer"
"github.com/spf13/cobra"
)

const transferPort = ":28125"

var SendCmd = &cobra.Command{
Use: "send <peer-ip> <file>",
Short: "Send a file to a peer",
Expand All @@ -22,54 +18,53 @@ var SendCmd = &cobra.Command{
peerIP := args[0]
filePath := args[1]

f, err := os.Open(filePath)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer f.Close()
// Render progress off a goroutine: the first snapshot prints the "Sending…"
// header, later snapshots overwrite a single live line via carriage return.
prog := make(chan transfer.Progress, 1)
renderDone := make(chan struct{})
go func() {
var lr transfer.LineRenderer
first := true
for p := range prog {
if first {
first = false
lr.Seed(p)
fmt.Printf("Sending %s (%s) to %s...\n", p.Name, transfer.FormatBytes(float64(p.Total)), peerIP)
continue
}
fmt.Print("\r" + lr.Line(p))
}
close(renderDone)
}()

info, err := f.Stat()
if err != nil {
fmt.Println("Error reading file info:", err)
return
}
res, err := transfer.Send(context.Background(), peerIP, filePath, prog)
close(prog)
<-renderDone

conn, err := net.DialTimeout("tcp", peerIP+transferPort, 5*time.Second)
if err != nil {
fmt.Printf("Peer %s is not receiving. Ask them to run 'blindspot receive'.\n", peerIP)
return
if enteredBody(err) {
fmt.Println()
}
defer conn.Close()

name := []byte(filepath.Base(filePath))
if err := binary.Write(conn, binary.BigEndian, uint16(len(name))); err != nil {
fmt.Println("Error sending filename length:", err)
return
}
if _, err := conn.Write(name); err != nil {
fmt.Println("Error sending filename:", err)
return
}
if err := binary.Write(conn, binary.BigEndian, uint64(info.Size())); err != nil {
fmt.Println("Error sending file size:", err)
return
}

fmt.Printf("Sending %s (%s) to %s...\n", info.Name(), formatBytes(float64(info.Size())), peerIP)
pw := &progressWriter{w: conn}
stop := startProgress(info.Size(), pw)
start := time.Now()
n, err := io.Copy(pw, f)
stop()
fmt.Println()
if err != nil {
fmt.Println("Error sending file:", err)
fmt.Println(err)
return
}
elapsed := time.Since(start)
fmt.Printf("Done — %s in %s (avg %s/s)\n",
formatBytes(float64(n)), elapsed.Round(time.Millisecond),
formatBytes(float64(n)/elapsed.Seconds()))
transfer.FormatBytes(float64(res.Bytes)), res.Elapsed.Round(time.Millisecond),
transfer.FormatBytes(float64(res.Bytes)/res.Elapsed.Seconds()))
},
}

// enteredBody reports whether a transfer got as far as streaming the file body — i.e. it
// succeeded, or failed during the copy. It gates the blank line that terminates the live
// progress display, so a failure before the body (dial, header, file create) prints no
// stray blank line, exactly as the pre-refactor CLI did.
func enteredBody(err error) bool {
if err == nil {
return true
}
var te *transfer.Error
if errors.As(err, &te) {
return te.Op == transfer.OpSendBody || te.Op == transfer.OpRecvBody
}
return false
}
67 changes: 0 additions & 67 deletions cmd/transfer.go

This file was deleted.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
)

require (
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
github.com/adrg/xdg v0.5.3 // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
};

export {
IncomingRequest,
Peer,
Status
} from "./models.js";
Loading
Loading