Skip to content

Commit

Permalink
feat: 接收功能完成
Browse files Browse the repository at this point in the history
feat: 发送文本内容自动粘贴到剪贴板
  • Loading branch information
meowrain committed Jun 16, 2024
1 parent 38c9e74 commit fa7d919
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 49 deletions.
58 changes: 58 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 项目名
PROJECT_NAME := localsend_cli

# 源代码目录
SRC_DIR := ./cmd

# 输出目录
OUT_DIR := ./bin

# Go 编译器
GO := go

# 目标平台
PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64

# 默认目标
.PHONY: all
all: clean build

# 清理
.PHONY: clean
clean:
rm -rf $(OUT_DIR)

# 创建输出目录
.PHONY: create-out-dir
create-out-dir:
mkdir -p $(OUT_DIR)

# 构建
.PHONY: build
build: create-out-dir $(PLATFORMS)

# 针对每个平台编译
$(PLATFORMS):
GOOS=$(word 1, $(subst /, ,$@)) GOARCH=$(word 2, $(subst /, ,$@)) \
$(GO) build -o $(OUT_DIR)/$(PROJECT_NAME)-$(word 1, $(subst /, ,$@))-$(word 2, $(subst /, ,$@))$(if $(findstring windows,$@),.exe) $(SRC_DIR)

# 测试
.PHONY: test
test:
$(GO) test ./...

# 安装依赖
.PHONY: deps
deps:
$(GO) mod tidy

# 使用方法
.PHONY: help
help:
@echo "Usage:"
@echo " make - 编译所有平台的可执行文件"
@echo " make clean - 清理输出目录"
@echo " make build - 编译所有平台的可执行文件"
@echo " make test - 运行测试"
@echo " make deps - 安装依赖"
@echo " make help - 显示此帮助信息"
7 changes: 5 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ func main() {

// 启动HTTP服务器
httpServer := server.New()
httpServer.HandleFunc("/upload", handlers.SendHandler) // 上传处理程序
httpServer.HandleFunc("/download", handlers.ReceiveHandler) // 下载处理程序
httpServer.HandleFunc("/api/localsend/v2/prepare-upload", handlers.PrepareReceive)
httpServer.HandleFunc("/api/localsend/v2/upload", handlers.ReceiveHandler)
httpServer.HandleFunc("/send", handlers.SendHandler) // 上传处理程序
httpServer.HandleFunc("/receive", handlers.NormalReceiveHandler) // 下载处理程序
httpServer.Handle("/", http.FileServer(http.Dir("static")))
go func() {
log.Println("Server started at :53317")
if err := http.ListenAndServe(":53317", httpServer); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ module localsend_cli

go 1.22

require github.com/prometheus-community/pro-bing v0.4.0

require (
github.com/google/uuid v1.6.0 // indirect
github.com/prometheus-community/pro-bing v0.4.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
Expand Down
32 changes: 0 additions & 32 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,42 +1,10 @@
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/j-keck/arping v1.0.3 h1:aeVk5WnsK6xPaRsFt5wV6W2x5l/n5XBNp0MMr/FEv2k=
github.com/j-keck/arping v1.0.3/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/mdlayher/arp v0.0.0-20220512170110-6706a2966875 h1:ql8x//rJsHMjS+qqEag8n3i4azw1QneKh5PieH9UEbY=
github.com/mdlayher/arp v0.0.0-20220512170110-6706a2966875/go.mod h1:kfOoFJuHWp76v1RgZCb9/gVUc7XdY877S2uVYbNliGc=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
github.com/mdlayher/socket v0.2.1 h1:F2aaOwb53VsBE+ebRS9bLd7yPOfYUMC8lOODdCBDY6w=
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
6 changes: 4 additions & 2 deletions internal/discovery/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func pingScan() ([]string, error) {
fmt.Println("Failed to create pinger:", err)
return
}
pinger.SetPrivileged(true)
pinger.Count = 1
pinger.Timeout = time.Second * 1

Expand Down Expand Up @@ -100,7 +101,7 @@ func StartHTTPBroadcast() {
DeviceType: "desktop",
Fingerprint: "random-string",
Port: 53317,
Protocol: "https",
Protocol: "http",
Download: true,
Announce: true,
}
Expand Down Expand Up @@ -129,7 +130,8 @@ func StartHTTPBroadcast() {
}

wg.Wait()
fmt.Println("HTTP broadcast messages sent!")
// log
// fmt.Println("HTTP broadcast messages sent!")
time.Sleep(5 * time.Second) // 每5秒发送一次HTTP广播消息
}
}
Expand Down
5 changes: 3 additions & 2 deletions internal/discovery/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func StartBroadcast() {
DeviceType: "desktop",
Fingerprint: "random-string",
Port: 53317,
Protocol: "https",
Protocol: "http",
Download: true,
Announce: true,
}
Expand All @@ -50,7 +50,8 @@ func StartBroadcast() {
panic(err)
}
// fmt.Println(num, "bytes write to multicastAddr")
fmt.Println("UDP Broadcast message sent!")
//log
// fmt.Println("UDP Broadcast message sent!")
time.Sleep(5 * time.Second) // 每5秒发送一次广播消息
}
}
Expand Down
111 changes: 110 additions & 1 deletion internal/handlers/receive.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,124 @@
package handlers

import (
"encoding/json"
"fmt"
"io"
"localsend_cli/internal/models"
"localsend_cli/internal/utils"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
)

// ReceiveHandler 处理文件下载请求
var (
sessionIDCounter = 0
sessionMutex sync.Mutex
fileNames = make(map[string]string) // 用于保存文件名
)

func PrepareReceive(w http.ResponseWriter, r *http.Request) {
var req models.PrepareReceiveRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
fmt.Println("Received request:", req)

sessionMutex.Lock()
sessionIDCounter++
sessionID := fmt.Sprintf("session-%d", sessionIDCounter)
sessionMutex.Unlock()

files := make(map[string]string)
for fileID, fileInfo := range req.Files {
token := fmt.Sprintf("token-%s", fileID)
files[fileID] = token

// 保存文件名
fileNames[fileID] = fileInfo.FileName

if strings.HasSuffix(fileInfo.FileName, ".txt") {
fmt.Println("TXT file content preview:", string(fileInfo.Preview))
utils.WriteToClipBoard(fileInfo.Preview)
}
}

resp := models.PrepareReceiveResponse{
SessionID: sessionID,
Files: files,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func ReceiveHandler(w http.ResponseWriter, r *http.Request) {
sessionID := r.URL.Query().Get("sessionId")
fileID := r.URL.Query().Get("fileId")
token := r.URL.Query().Get("token")

// 验证请求参数
if sessionID == "" || fileID == "" || token == "" {
http.Error(w, "Missing parameters", http.StatusBadRequest)
return
}

// 使用 fileID 获取文件名
fileName, ok := fileNames[fileID]
if !ok {
http.Error(w, "Invalid file ID", http.StatusBadRequest)
return
}

// 生成文件路径,保留文件扩展名
filePath := filepath.Join("uploads", fileName)
// 创建文件夹(如果不存在)
dir := filepath.Dir(filePath)
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
http.Error(w, "Failed to create directory", http.StatusInternalServerError)
fmt.Println("Error creating directory:", err)
return
}
// 创建文件
file, err := os.Create(filePath)
if err != nil {
http.Error(w, "Failed to create file", http.StatusInternalServerError)
fmt.Println("Error creating file:", err)
return
}
defer file.Close()

buffer := make([]byte, 2*1024*1024) // 2MB 缓冲区
for {
n, err := r.Body.Read(buffer)
if err != nil && err != io.EOF {
http.Error(w, "Failed to read file", http.StatusInternalServerError)
fmt.Println("Error reading file:", err)
return
}
if n == 0 {
break
}

_, err = file.Write(buffer[:n])
if err != nil {
http.Error(w, "Failed to write file", http.StatusInternalServerError)
fmt.Println("Error writing file:", err)
return

}
}

fmt.Println("Saved file:", filePath)
w.WriteHeader(http.StatusOK)

}

// ReceiveHandler 处理文件下载请求
func NormalReceiveHandler(w http.ResponseWriter, r *http.Request) {
fileName := r.URL.Query().Get("file")
if fileName == "" {
http.Error(w, "File parameter is required", http.StatusBadRequest)
Expand Down
8 changes: 3 additions & 5 deletions internal/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ type DeviceInfo struct {
Protocol string `json:"protocol"`
Download bool `json:"download"`
}

type FileInfo struct {
ID string `json:"id"`
FileName string `json:"fileName"`
Size int64 `json:"size"`
FileType string `json:"fileType"`
Sha256 string `json:"sha256,omitempty"`
SHA256 string `json:"sha256,omitempty"`
Preview string `json:"preview,omitempty"`
}

type PrepareUploadRequest struct {
type PrepareReceiveRequest struct {
Info struct {
Alias string `json:"alias"`
Version string `json:"version"`
Expand All @@ -46,7 +44,7 @@ type PrepareUploadRequest struct {
Files map[string]FileInfo `json:"files"`
}

type PrepareUploadResponse struct {
type PrepareReceiveResponse struct {
SessionID string `json:"sessionId"`
Files map[string]string `json:"files"` // File ID to Token map
}
44 changes: 44 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1 +1,45 @@
package utils

import (
"fmt"
"os/exec"
"runtime"
"strings"
)

func CheckOSType() string {
return runtime.GOOS
}
func WriteToClipBoard(text string) {
os := CheckOSType()
switch os {
case "linux":
cmd := exec.Command("xclip", "-selection", "clipboard")
cmd.Stdin = strings.NewReader(text)
err := cmd.Run()
if err != nil {
fmt.Printf("Error copying to clipboard on Linux: %v\n", err)
} else {
fmt.Println("Text copied to clipboard on Linux!")
}
case "windows":
cmd := exec.Command("cmd", "/c", "echo "+text+" | clip")
err := cmd.Run()
if err != nil {
fmt.Printf("Error copying to clipboard on Windows: %v\n", err)
} else {
fmt.Println("Text copied to clipboard on Windows!")
}
case "darwin":
cmd := exec.Command("pbcopy")
cmd.Stdin = strings.NewReader(text)
err := cmd.Run()
if err != nil {
fmt.Printf("Error copying to clipboard on macOS: %v\n", err)
} else {
fmt.Println("Text copied to clipboard on macOS!")
}
default:
fmt.Printf("Unsupported OS: %v\n", os)
}
}
Binary file modified localsend_cli
Binary file not shown.
Loading

0 comments on commit fa7d919

Please sign in to comment.