12 changed files with 1131 additions and 10 deletions
@ -0,0 +1,2 @@ |
|||||
|
## 文件升级的管理端 |
||||
|
管理待升级的文件,可以勾选和其他的操作 |
||||
@ -0,0 +1,73 @@ |
|||||
|
package config |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"net" |
||||
|
"os" |
||||
|
) |
||||
|
|
||||
|
type Config struct { |
||||
|
DeviceName string |
||||
|
Port string |
||||
|
LocalIP string |
||||
|
MulticastAddress string |
||||
|
WildcardAddress string |
||||
|
FilePath string |
||||
|
Version string |
||||
|
} |
||||
|
|
||||
|
var G Config |
||||
|
|
||||
|
func (c *Config) SetConf(port string) error { |
||||
|
Hostname, err := os.Hostname() |
||||
|
if err != nil { |
||||
|
Hostname = "unknow device" |
||||
|
} |
||||
|
// 设备名称
|
||||
|
c.DeviceName = Hostname |
||||
|
c.Port = port |
||||
|
c.LocalIP, err = getLocalIP() |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
// 多播地址
|
||||
|
c.MulticastAddress = fmt.Sprintf("224.0.0.169:%s", port) |
||||
|
// 广播地址
|
||||
|
c.WildcardAddress = fmt.Sprintf("0.0.0.0:%s", port) |
||||
|
c.FilePath = "./" |
||||
|
c.Version = "0.3.0" |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// 本地ip
|
||||
|
func getLocalIP() (string, error) { |
||||
|
addrs, err := net.InterfaceAddrs() |
||||
|
if err != nil { |
||||
|
return "", err |
||||
|
} |
||||
|
var ips []string |
||||
|
for _, address := range addrs { |
||||
|
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { |
||||
|
if ipnet.IP.To4() != nil { |
||||
|
ips = append(ips, ipnet.IP.String()) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if len(ips) == 0 { |
||||
|
return "", fmt.Errorf("get local ip failed") |
||||
|
} else if len(ips) == 1 { |
||||
|
return ips[0], nil |
||||
|
} else { |
||||
|
// Select the one connected to the network
|
||||
|
// when there are multiple network interfaces
|
||||
|
|
||||
|
// Is there a better way?
|
||||
|
c, err := net.Dial("udp", "8.8.8.8:80") |
||||
|
if err != nil { |
||||
|
return ips[0], nil |
||||
|
} |
||||
|
defer c.Close() |
||||
|
return c.LocalAddr().(*net.UDPAddr).IP.String(), nil |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
module fssc |
||||
|
|
||||
|
go 1.22.1 |
||||
|
|
||||
|
require github.com/urfave/cli/v2 v2.27.2 |
||||
|
|
||||
|
require ( |
||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect |
||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect |
||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect |
||||
|
) |
||||
@ -0,0 +1,8 @@ |
|||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= |
||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= |
||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= |
||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
||||
|
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= |
||||
|
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= |
||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= |
||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= |
||||
@ -0,0 +1,104 @@ |
|||||
|
package discovery |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"net" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/chyok/st/config" |
||||
|
"github.com/chyok/st/internal/transfer" |
||||
|
) |
||||
|
|
||||
|
const separator = "|" |
||||
|
|
||||
|
type Role string |
||||
|
|
||||
|
const ( |
||||
|
Sender Role = "sender" |
||||
|
Receiver Role = "receiver" |
||||
|
) |
||||
|
|
||||
|
func Listen(role Role, filePath string) { |
||||
|
addr, err := net.ResolveUDPAddr("udp", config.G.MulticastAddress) |
||||
|
|
||||
|
if err != nil { |
||||
|
fmt.Printf("Failed to resolve %s: %v\n", config.G.MulticastAddress, err) |
||||
|
return |
||||
|
} |
||||
|
conn, err := net.ListenMulticastUDP("udp", nil, addr) |
||||
|
if err != nil { |
||||
|
fmt.Printf("Failed to listen on %s: %v\n", config.G.MulticastAddress, err) |
||||
|
return |
||||
|
} |
||||
|
defer conn.Close() |
||||
|
|
||||
|
buf := make([]byte, 1024) |
||||
|
for { |
||||
|
n, src, err := conn.ReadFromUDP(buf) |
||||
|
|
||||
|
remoteAddr := src.IP.String() + ":" + config.G.Port |
||||
|
|
||||
|
if err != nil { |
||||
|
fmt.Printf("Failed to read from %s: %v\n", remoteAddr, err) |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
message := string(buf[:n]) |
||||
|
parts := strings.Split(message, separator) |
||||
|
if len(parts) != 2 { |
||||
|
fmt.Printf("Received malformed message from %s: %s\n", remoteAddr, message) |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
deviceName := parts[0] |
||||
|
remoteRole := Role(parts[1]) |
||||
|
switch remoteRole { |
||||
|
case Sender: |
||||
|
if role == Sender { |
||||
|
// 发现发送端
|
||||
|
fmt.Printf("Discovered Sender: %s (%s)\n", deviceName, remoteAddr) |
||||
|
go func() { |
||||
|
err := transfer.ReceiveFiles(remoteAddr) |
||||
|
if err != nil { |
||||
|
fmt.Printf("Receive file from %s error: %s\n", remoteAddr, err) |
||||
|
} |
||||
|
}() |
||||
|
} |
||||
|
case Receiver: |
||||
|
if role == Receiver { |
||||
|
// 发现接收端
|
||||
|
fmt.Printf("Discovered Receiver: %s (%s)\n", deviceName, remoteAddr) |
||||
|
go func() { |
||||
|
err := transfer.SendFiles(filePath, fmt.Sprintf("http://%s", remoteAddr)) |
||||
|
if err != nil { |
||||
|
fmt.Printf("Send file to %s error: %s\n", remoteAddr, err) |
||||
|
} |
||||
|
}() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func Send(role Role) { |
||||
|
addr, err := net.ResolveUDPAddr("udp", config.G.MulticastAddress) |
||||
|
if err != nil { |
||||
|
fmt.Printf("Failed to resolve %s: %v\n", config.G.MulticastAddress, err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
localAddr, err := net.ResolveUDPAddr("udp", config.G.LocalIP+":0") |
||||
|
if err != nil { |
||||
|
fmt.Printf("Failed to resolve %s: %v\n", config.G.MulticastAddress, err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
conn, err := net.DialUDP("udp", localAddr, addr) |
||||
|
if err != nil { |
||||
|
fmt.Printf("Failed to dial %s: %v\n", config.G.MulticastAddress, err) |
||||
|
return |
||||
|
} |
||||
|
defer conn.Close() |
||||
|
|
||||
|
message := fmt.Sprintf("%s%s%s", config.G.DeviceName, separator, role) |
||||
|
conn.Write([]byte(message)) |
||||
|
} |
||||
@ -0,0 +1,395 @@ |
|||||
|
package handler |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"html/template" |
||||
|
"io" |
||||
|
"mime" |
||||
|
"net" |
||||
|
"path" |
||||
|
"strings" |
||||
|
"time" |
||||
|
|
||||
|
"net/http" |
||||
|
"net/rpc" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
|
||||
|
"github.com/chyok/st/config" |
||||
|
"github.com/chyok/st/internal/transfer" |
||||
|
"github.com/chyok/st/internal/util" |
||||
|
"github.com/chyok/st/web" |
||||
|
) |
||||
|
|
||||
|
// rpc功能 压缩包的结构体
|
||||
|
// 文件路径 文件名
|
||||
|
type Args struct { |
||||
|
Zpfile, Fname string |
||||
|
} |
||||
|
|
||||
|
// 获取文件大小的接口
|
||||
|
type Size interface { |
||||
|
Size() int64 |
||||
|
} |
||||
|
|
||||
|
// 获取文件信息的接口
|
||||
|
type Stat interface { |
||||
|
Stat() (os.FileInfo, error) |
||||
|
} |
||||
|
|
||||
|
// 显示状态
|
||||
|
func ReceiveStatus(w http.ResponseWriter, r *http.Request) { |
||||
|
switch r.Method { |
||||
|
// 显示当前传送文件的大小
|
||||
|
case http.MethodPost: |
||||
|
file, _, err := r.FormFile("file") |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), 500) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if statInterface, ok := file.(Stat); ok { |
||||
|
fileInfo, _ := statInterface.Stat() |
||||
|
fmt.Fprintf(w, "上传文件的大小为: %d", fileInfo.Size()) |
||||
|
} |
||||
|
|
||||
|
if sizeInterface, ok := file.(Size); ok { |
||||
|
fmt.Fprintf(w, "上传文件的大小为: %d", sizeInterface.Size()) |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// fmt.Fprintf(w, r.Method)
|
||||
|
} |
||||
|
|
||||
|
// 接收 上传
|
||||
|
func ReceiveHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
switch r.Method { |
||||
|
case http.MethodGet: |
||||
|
// serve upload page for receive
|
||||
|
// tmpl, err := template.New("index").Parse(web.UploadPage)
|
||||
|
// if err != nil {
|
||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
// return
|
||||
|
// }
|
||||
|
// // 获取当前设备名称
|
||||
|
// err = tmpl.Execute(w, config.G.DeviceName)
|
||||
|
// if err != nil {
|
||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
// }
|
||||
|
//
|
||||
|
fmt.Fprintf(w, "%s:接收文件中...", r.Host) |
||||
|
case http.MethodPost: |
||||
|
// receive file and save
|
||||
|
file, header, err := r.FormFile("file") |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusBadRequest) |
||||
|
return |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
_, params, err := mime.ParseMediaType(header.Header.Get("Content-Disposition")) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusBadRequest) |
||||
|
return |
||||
|
} |
||||
|
filename := filepath.FromSlash(params["filename"]) |
||||
|
|
||||
|
fmt.Printf("Downloading [%s]...\n", filename) |
||||
|
dirPath := filepath.Dir(filename) |
||||
|
err = os.MkdirAll(dirPath, os.ModePerm) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
out, err := os.Create(filename) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
defer out.Close() |
||||
|
|
||||
|
_, err = io.Copy(out, file) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
// 如果收到的是zip文件,自动给解压缩
|
||||
|
suf := strings.Split(filename, ".") |
||||
|
if suf[1] == "zip" { |
||||
|
go util.DecompressZip(filename) |
||||
|
} |
||||
|
|
||||
|
fmt.Printf("[√] Download [%s] Success.\n", filename) |
||||
|
//
|
||||
|
default: |
||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
||||
|
} |
||||
|
|
||||
|
// 输出接收结果
|
||||
|
fmt.Fprintf(w, "接收成功,并已经完成解压缩") |
||||
|
} |
||||
|
|
||||
|
// 文件服务
|
||||
|
func FileServerHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
currentPath := config.G.FilePath |
||||
|
|
||||
|
fileInfo, err := os.Stat(currentPath) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
basePath := filepath.Base(currentPath) |
||||
|
if fileInfo.IsDir() { |
||||
|
path := r.URL.Path[len("/download/"+basePath):] |
||||
|
fullPath := filepath.Join(currentPath, path) |
||||
|
http.ServeFile(w, r, fullPath) |
||||
|
} else { |
||||
|
http.ServeFile(w, r, currentPath) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// udp 方式发送zip文件
|
||||
|
func SendZip(w http.ResponseWriter, r *http.Request) { |
||||
|
r.ParseForm() |
||||
|
// 选择文件,并生成zip包
|
||||
|
// 文件
|
||||
|
zipfarr := r.Form["zipfiles"] |
||||
|
|
||||
|
// 服务器ip地址
|
||||
|
serip := r.Form["serverip"] |
||||
|
if serip[0] == "" { |
||||
|
http.Error(w, "remote server ip is blank!", http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 选中的路径,可以为空
|
||||
|
wtculpath := r.Form["curpath"] |
||||
|
|
||||
|
// 实际路径
|
||||
|
realFilePath := filepath.Join(config.G.FilePath, wtculpath[0]) |
||||
|
|
||||
|
// zip 文件名
|
||||
|
zpFileName := "BIU_" + time.Now().Format("20060102_150405") + ".zip" |
||||
|
|
||||
|
// 创建zip 异步?
|
||||
|
taskId := make(chan string) |
||||
|
go func() { |
||||
|
util.CompressToZip(zpFileName, realFilePath, zipfarr) |
||||
|
taskId <- "arcok" |
||||
|
// fmt.Fprintln(w, "create archive:", err)
|
||||
|
}() |
||||
|
// go util.CompressToZip(zpFileName, realFilePath, zipfarr)
|
||||
|
fmt.Println("archive is createding...") |
||||
|
|
||||
|
// 当前运行的目录
|
||||
|
|
||||
|
// ZIP 文件的实际路径
|
||||
|
ziprl := path.Join("./files/", zpFileName) |
||||
|
|
||||
|
// zip 创建成功后
|
||||
|
rest := <-taskId |
||||
|
// 有压缩包 才可以操作
|
||||
|
if strings.EqualFold(strings.ToLower(rest), "arcok") { |
||||
|
fmt.Println("archive is sending...") |
||||
|
// 创建udp 渠道发送数据
|
||||
|
// 1、获取udp addr
|
||||
|
remoteAddr, err := net.ResolveUDPAddr("udp", serip[0]+":9099") |
||||
|
if err != nil { |
||||
|
fmt.Printf("Failed to resolve %s: %v\n", serip[0], err) |
||||
|
return |
||||
|
} |
||||
|
// 2、 监听端口
|
||||
|
conn, err := net.DialUDP("udp", nil, remoteAddr) |
||||
|
if err != nil { |
||||
|
fmt.Printf("Failed to dial %s: %v\n", serip[0], err) |
||||
|
return |
||||
|
} |
||||
|
defer conn.Close() |
||||
|
// 3、在端口发送数据
|
||||
|
message := fmt.Sprintf("%s%s%s", config.G.DeviceName, "|", "sender") |
||||
|
// 向链接通道发送数据 数据包头
|
||||
|
conn.Write([]byte(message)) |
||||
|
// 发送文件
|
||||
|
go func() { |
||||
|
err := transfer.SendFiles(ziprl, fmt.Sprintf("http://%s", remoteAddr)) |
||||
|
if err != nil { |
||||
|
fmt.Printf("Send file to %s error: %s\n", remoteAddr, err) |
||||
|
} |
||||
|
}() |
||||
|
// 页面上显示
|
||||
|
fmt.Fprintf(w, "File:%s,been sent successfully.", zpFileName) |
||||
|
} else { |
||||
|
fmt.Println("archive is not exist!!!") |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// 下载zip
|
||||
|
func Downzip(w http.ResponseWriter, r *http.Request) { |
||||
|
r.ParseForm() |
||||
|
// 选择文件,并生成zip包
|
||||
|
// 文件
|
||||
|
zipfarr := r.Form["zipfiles"] |
||||
|
// 服务器ip地址
|
||||
|
serip := r.Form["serverip"] |
||||
|
if serip[0] == "" { |
||||
|
http.Error(w, "remote server ip is blank!", http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
// fmt.Println(r.Form)
|
||||
|
// 选中的路径
|
||||
|
wtculpath := r.Form["curpath"] |
||||
|
|
||||
|
// 实际路径
|
||||
|
realFilePath := filepath.Join(config.G.FilePath, wtculpath[0]) |
||||
|
|
||||
|
// zip 文件名
|
||||
|
zpFileName := "BIU_" + time.Now().Format("20060102_150405") + ".zip" |
||||
|
|
||||
|
// 创建zip
|
||||
|
err := util.CompressToZip(zpFileName, realFilePath, zipfarr) |
||||
|
if err != nil { |
||||
|
fmt.Println("create zip error", err) |
||||
|
} |
||||
|
// 停止下,检查zip是否创建成功
|
||||
|
time.Sleep(1200) |
||||
|
_, err = os.Stat(realFilePath + "/" + zpFileName) |
||||
|
if err != nil { |
||||
|
|
||||
|
// 调用httpd rpc
|
||||
|
fmt.Println("RPC Server IP:", serip[0]) |
||||
|
rpcServer := serip[0] + ":9080" |
||||
|
client, err := rpc.DialHTTP("tcp", rpcServer) |
||||
|
if err != nil { |
||||
|
fmt.Println("Http rpc error :", err) |
||||
|
} |
||||
|
// 组装url 静态文件
|
||||
|
// url := "http://" + config.G.LocalIP + ":" + config.G.Port + "/files/" + zpFileName
|
||||
|
// 动态
|
||||
|
url := "http://" + config.G.LocalIP + ":" + config.G.Port + "/files?zp=" + zpFileName |
||||
|
args := Args{url, zpFileName} |
||||
|
var reply int |
||||
|
err = client.Call("Rbup.DecompressZip", args, &reply) |
||||
|
if err != nil { |
||||
|
fmt.Println("rpc call error :", err) |
||||
|
// 输出执行结果
|
||||
|
fmt.Fprintln(w, "rpc call error:", err) |
||||
|
} else { |
||||
|
// 输出下载链接
|
||||
|
fmt.Fprintf(w, "<a href='%s'>%s</a><br/>\n", url, url) |
||||
|
fmt.Fprintf(w, "RPC execute result :%d<br/>\n", reply) |
||||
|
} |
||||
|
} else { |
||||
|
fmt.Fprintf(w, "archive is created faild") |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// 文件下载
|
||||
|
func Dfiles(w http.ResponseWriter, r *http.Request) { |
||||
|
// url中获取参数
|
||||
|
query := r.URL.Query() |
||||
|
zpfile := query.Get("zp") |
||||
|
|
||||
|
// 实际路径
|
||||
|
zpFileName := filepath.Join(config.G.FilePath, "/files/", zpfile) |
||||
|
// fileInfo, err := os.Stat(zpFileName)
|
||||
|
// if err != nil {
|
||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
// return
|
||||
|
// }
|
||||
|
// fmt.Println(fileInfo)
|
||||
|
http.ServeFile(w, r, zpFileName) |
||||
|
} |
||||
|
|
||||
|
// 发送拦截
|
||||
|
func SendHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
switch r.Method { |
||||
|
case http.MethodGet: |
||||
|
// serve download page for send
|
||||
|
realFilePath := filepath.Join(config.G.FilePath, r.URL.Path[1:]) |
||||
|
downloadPath := filepath.Join(filepath.Base(config.G.FilePath), r.URL.Path[1:]) |
||||
|
fileInfo, err := os.Stat(realFilePath) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
data := struct { |
||||
|
DeviceName string |
||||
|
//Rundir string
|
||||
|
IsDir bool |
||||
|
FileName string |
||||
|
DownloadPath string |
||||
|
UrlPath string |
||||
|
Files []os.DirEntry |
||||
|
}{ |
||||
|
DeviceName: config.G.DeviceName, |
||||
|
//Rundir: config.G.FilePath,
|
||||
|
DownloadPath: downloadPath, |
||||
|
UrlPath: strings.TrimSuffix(r.URL.Path, "/"), |
||||
|
} |
||||
|
|
||||
|
if fileInfo.IsDir() { |
||||
|
data.IsDir = true |
||||
|
// 遍历目录
|
||||
|
files, err := os.ReadDir(realFilePath) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
data.Files = files |
||||
|
} else { |
||||
|
data.FileName = filepath.Base(realFilePath) |
||||
|
} |
||||
|
|
||||
|
//
|
||||
|
fmt.Print("fdata:%v", data) |
||||
|
|
||||
|
// 文件列表模板
|
||||
|
tmpl, err := template.New("download").Parse(web.DownloadPage) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
err = tmpl.Execute(w, data) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
} |
||||
|
case http.MethodPost: |
||||
|
// return file or folder information in JSON format for convenient send to the recipient
|
||||
|
currentPath := config.G.FilePath |
||||
|
fileInfo, err := os.Stat(currentPath) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
var pathInfo struct { |
||||
|
Type string `json:"type"` |
||||
|
Paths []string `json:"paths"` |
||||
|
} |
||||
|
|
||||
|
if fileInfo.IsDir() { |
||||
|
files, err := util.GetDirFilePaths(currentPath, true) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
pathInfo.Paths = files |
||||
|
pathInfo.Type = "dir" |
||||
|
} else { |
||||
|
pathInfo.Paths = []string{filepath.Base(currentPath)} |
||||
|
pathInfo.Type = "file" |
||||
|
} |
||||
|
|
||||
|
w.Header().Set("Content-Type", "application/json") |
||||
|
json.NewEncoder(w).Encode(pathInfo) |
||||
|
default: |
||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
package transfer |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
|
||||
|
"io" |
||||
|
"net/http" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
) |
||||
|
|
||||
|
// 接收文件
|
||||
|
func ReceiveFiles(remoteAddr string) error { |
||||
|
|
||||
|
resp, err := http.Post(fmt.Sprintf("http://%s/", remoteAddr), "", nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer resp.Body.Close() |
||||
|
|
||||
|
if resp.StatusCode != http.StatusOK { |
||||
|
return fmt.Errorf("failed to get file info: %s", resp.Status) |
||||
|
} |
||||
|
|
||||
|
var pathInfo struct { |
||||
|
Type string `json:"type"` |
||||
|
Paths []string `json:"paths"` |
||||
|
} |
||||
|
|
||||
|
if err := json.NewDecoder(resp.Body).Decode(&pathInfo); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
if pathInfo.Type == "dir" { |
||||
|
fmt.Printf("Found directory with %d paths:\n", len(pathInfo.Paths)) |
||||
|
for _, path := range pathInfo.Paths { |
||||
|
fmt.Printf("- %s\n", path) |
||||
|
} |
||||
|
fmt.Print("Do you want to download the entire directory? [y/n] ") |
||||
|
var confirm string |
||||
|
if _, err := fmt.Scanln(&confirm); err != nil || (confirm != "y" && confirm != "Y") { |
||||
|
return nil |
||||
|
} |
||||
|
for _, path := range pathInfo.Paths { |
||||
|
if err := downloadFile(remoteAddr, path); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
if len(pathInfo.Paths) != 1 { |
||||
|
return fmt.Errorf("unexpected number of paths: %d", len(pathInfo.Paths)) |
||||
|
} |
||||
|
if err := downloadFile(remoteAddr, pathInfo.Paths[0]); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// 下载文件
|
||||
|
func downloadFile(remoteAddr, path string) error { |
||||
|
path = filepath.ToSlash(path) |
||||
|
resp, err := http.Get(fmt.Sprintf("http://%s/download/%s", remoteAddr, path)) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer resp.Body.Close() |
||||
|
|
||||
|
if resp.StatusCode != http.StatusOK { |
||||
|
return fmt.Errorf("failed to download file: %s", resp.Status) |
||||
|
} |
||||
|
|
||||
|
fmt.Printf("Downloading [%s]...\n", path) |
||||
|
out, err := os.Create(path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer out.Close() |
||||
|
|
||||
|
_, err = io.Copy(out, resp.Body) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
fmt.Printf("[✅] Download [%s] Success.\n", path) |
||||
|
return nil |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
package transfer |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"mime/multipart" |
||||
|
"net/http" |
||||
|
"os" |
||||
|
"path" |
||||
|
"path/filepath" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/chyok/st/internal/util" |
||||
|
"github.com/schollz/progressbar/v3" |
||||
|
) |
||||
|
|
||||
|
// 发送文件
|
||||
|
func SendFiles(filePath string, url string) error { |
||||
|
filePath = filepath.ToSlash(filePath) |
||||
|
fileInfo, err := os.Stat(filePath) |
||||
|
if err != nil { |
||||
|
if os.IsNotExist(err) { |
||||
|
return fmt.Errorf("file [%s] not exist", filePath) |
||||
|
} |
||||
|
return fmt.Errorf("file [%s] error: %w", filePath, err) |
||||
|
} |
||||
|
|
||||
|
if fileInfo.IsDir() { |
||||
|
return postDirectory(filePath, url) |
||||
|
} |
||||
|
|
||||
|
return postFile(filePath, path.Base(filePath), url) |
||||
|
} |
||||
|
|
||||
|
// 传送文件夹
|
||||
|
func postDirectory(dirPath string, url string) error { |
||||
|
files, err := util.GetDirFilePaths(dirPath, false) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
fmt.Println("\nAll files in folder:") |
||||
|
for _, file := range files { |
||||
|
fmt.Println(file) |
||||
|
} |
||||
|
|
||||
|
var confirm string |
||||
|
fmt.Print("\nTransfer all files? [Y/N] ") |
||||
|
fmt.Scanln(&confirm) |
||||
|
if strings.ToLower(confirm) != "y" { |
||||
|
fmt.Print("\nCancel send all files ") |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
for _, file := range files { |
||||
|
fileName, _ := filepath.Rel(dirPath, file) |
||||
|
fileName = filepath.Join(filepath.Base(dirPath), fileName) |
||||
|
err := postFile(file, fileName, url) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
fmt.Printf("Send folder %s success.\n", dirPath) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// 传送文件
|
||||
|
func postFile(filePath string, filename string, url string) error { |
||||
|
payload := &bytes.Buffer{} |
||||
|
writer := multipart.NewWriter(payload) |
||||
|
|
||||
|
file, err := os.Open(filePath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
part, err := writer.CreateFormFile("file", filepath.ToSlash(filename)) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
fileInfo, _ := file.Stat() |
||||
|
bar := progressbar.DefaultBytes( |
||||
|
fileInfo.Size(), |
||||
|
fmt.Sprintf("Uploading [%s]", filename), |
||||
|
) |
||||
|
|
||||
|
_, err = io.Copy(io.MultiWriter(part, bar), file) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
err = writer.Close() |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
req, err := http.NewRequest(http.MethodPost, url, payload) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
req.Header.Set("Content-Type", writer.FormDataContentType()) |
||||
|
|
||||
|
client := &http.Client{} |
||||
|
resp, err := client.Do(req) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer resp.Body.Close() |
||||
|
|
||||
|
if resp.StatusCode != http.StatusOK { |
||||
|
return fmt.Errorf("upload failed with status code: %d", resp.StatusCode) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
@ -0,0 +1,233 @@ |
|||||
|
package util |
||||
|
|
||||
|
import ( |
||||
|
"archive/zip" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"io/fs" |
||||
|
"os" |
||||
|
"path" |
||||
|
"path/filepath" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
// 遍历目录下的文件
|
||||
|
func GetDirFilePaths(dirPath string, relativeOnly bool) ([]string, error) { |
||||
|
var filePaths []string |
||||
|
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
if !info.IsDir() { |
||||
|
if relativeOnly { |
||||
|
fileName := filepath.Base(path) |
||||
|
relativePath := filepath.ToSlash(filepath.Join(filepath.Base(dirPath), fileName)) |
||||
|
filePaths = append(filePaths, relativePath) |
||||
|
} else { |
||||
|
filePaths = append(filePaths, filepath.ToSlash(path)) |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
return filePaths, nil |
||||
|
} |
||||
|
|
||||
|
// dest:目的地址以及压缩文件名 eg:/data/logZip/2022-12-12-log.zip
|
||||
|
// paths:需要压缩的所有文件所组成的集合
|
||||
|
func CompressToZip(dest string, currentPath string, paths []string) error { |
||||
|
// fmt.Println("real path", currentPath)
|
||||
|
// 打开files 目录
|
||||
|
filesPath := "" |
||||
|
if dir, err := os.Getwd(); err == nil { |
||||
|
filesPath = dir + "/files/" |
||||
|
} |
||||
|
if err := os.MkdirAll(filesPath, 0755); err != nil { |
||||
|
fmt.Println(err.Error()) |
||||
|
} |
||||
|
// ToSlash 过滤windows的斜杠引起的bug
|
||||
|
zfile, err := os.Create(path.Join("./files", "/", dest)) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer zfile.Close() |
||||
|
zipWriter := zip.NewWriter(zfile) |
||||
|
defer zipWriter.Close() |
||||
|
|
||||
|
// 遍历带压缩的目录信息
|
||||
|
for _, src := range paths { |
||||
|
// 替换文件名,并且去除前后 "\" 或 "/"
|
||||
|
// path = strings.Trim(path, string(filepath.Separator))
|
||||
|
// 从配置中读取到源目录
|
||||
|
src = path.Join(currentPath, "/", src) |
||||
|
// 删除尾随路径(如果它是目录)
|
||||
|
src := strings.TrimSuffix(src, string(os.PathSeparator)) |
||||
|
err = filepath.Walk(src, func(path string, info os.FileInfo, err error) error { |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
// 创建本地文件头
|
||||
|
header, err := zip.FileInfoHeader(info) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
// 将压缩方法设置为deflate
|
||||
|
header.Method = zip.Deflate |
||||
|
|
||||
|
// 在zip存档中设置文件的相对路径
|
||||
|
header.Name, err = filepath.Rel(filepath.Dir(src), path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
// 目录需要拼上一个 "/" ,否则会出现一个和目录一样的文件在压缩包中
|
||||
|
if info.IsDir() { |
||||
|
// header.Name += string(os.PathSeparator)
|
||||
|
header.Name += "/" |
||||
|
} else { |
||||
|
// 替换一下分隔符,zip不支持 "\\"
|
||||
|
header.Name = strings.ReplaceAll(header.Name, "\\", "/") |
||||
|
} |
||||
|
// 创建写入头的writer
|
||||
|
headerWriter, err := zipWriter.CreateHeader(header) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
if info.IsDir() { |
||||
|
return nil |
||||
|
} |
||||
|
f, err := os.Open(path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer f.Close() |
||||
|
_, err = io.Copy(headerWriter, f) |
||||
|
return err |
||||
|
}) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
// 存到指定位置
|
||||
|
os.Rename(dest, path.Join("./files/", dest)) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// 解压缩zip文件
|
||||
|
// 这个方法必须是首字母大写的,才能被注册
|
||||
|
// src: 需要解压的zip文件
|
||||
|
func DecompressZip(zpFname string) error { |
||||
|
// 下载文件
|
||||
|
archive, err := zip.OpenReader(zpFname) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
dir := filepath.Dir(zpFname) |
||||
|
defer archive.Close() |
||||
|
// 遍历目录
|
||||
|
for _, f := range archive.File { |
||||
|
filePath := filepath.Join(dir, f.Name) |
||||
|
if f.FileInfo().IsDir() { |
||||
|
os.MkdirAll(filePath, os.ModePerm) |
||||
|
continue |
||||
|
} |
||||
|
// 父文件夹开始闯将目录
|
||||
|
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { |
||||
|
return fmt.Errorf("failed to make directory (%v)", err) |
||||
|
} |
||||
|
// 文件原有模式
|
||||
|
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to create file (%v)", err) |
||||
|
} |
||||
|
fileInArchive, err := f.Open() |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to open file in zip (%v)", err) |
||||
|
} |
||||
|
if _, err := io.Copy(dstFile, fileInArchive); err != nil { |
||||
|
return fmt.Errorf("failed to copy file in zip (%v)", err) |
||||
|
} |
||||
|
dstFile.Close() |
||||
|
fileInArchive.Close() |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Zip 压缩文件或目录
|
||||
|
// @params dst io.Writer 压缩文件可写流
|
||||
|
// @params src string 待压缩源文件/目录路径
|
||||
|
func Zip(dst io.Writer, src string) error { |
||||
|
// 强转一下路径
|
||||
|
src = filepath.Clean(src) |
||||
|
// 提取最后一个文件或目录的名称
|
||||
|
baseFile := filepath.Base(src) |
||||
|
// 判断src是否存在
|
||||
|
_, err := os.Stat(src) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
// 通文件流句柄创建一个ZIP压缩包
|
||||
|
zw := zip.NewWriter(dst) |
||||
|
// 延迟关闭这个压缩包
|
||||
|
defer zw.Close() |
||||
|
|
||||
|
// 通过filepath封装的Walk来递归处理源路径到压缩文件中
|
||||
|
return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error { |
||||
|
// 是否存在异常
|
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
// 通过原始文件头信息,创建zip文件头信息
|
||||
|
zfh, err := zip.FileInfoHeader(info) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
// 赋值默认的压缩方法,否则不压缩
|
||||
|
zfh.Method = zip.Deflate |
||||
|
|
||||
|
// 移除绝对路径
|
||||
|
tmpPath := path |
||||
|
index := strings.Index(tmpPath, baseFile) |
||||
|
if index > -1 { |
||||
|
tmpPath = tmpPath[index:] |
||||
|
} |
||||
|
// 替换文件名,并且去除前后 "\" 或 "/"
|
||||
|
tmpPath = strings.Trim(tmpPath, string(filepath.Separator)) |
||||
|
// 替换一下分隔符,zip不支持 "\\"
|
||||
|
zfh.Name = strings.ReplaceAll(tmpPath, "\\", "/") |
||||
|
// 目录需要拼上一个 "/" ,否则会出现一个和目录一样的文件在压缩包中
|
||||
|
if info.IsDir() { |
||||
|
zfh.Name += "/" |
||||
|
} |
||||
|
|
||||
|
// 写入文件头信息,并返回一个ZIP文件写入句柄
|
||||
|
zfw, err := zw.CreateHeader(zfh) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
// 仅在他是标准文件时进行文件内容写入
|
||||
|
if zfh.Mode().IsRegular() { |
||||
|
// 打开要压缩的文件
|
||||
|
sfr, err := os.Open(path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer sfr.Close() |
||||
|
|
||||
|
// 将srcFileReader拷贝到zipFilWrite中
|
||||
|
_, err = io.Copy(zfw, sfr) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 搞定
|
||||
|
return nil |
||||
|
}) |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
package fssc |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"fssc/config" |
||||
|
"net/http" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
|
||||
|
"fssc/internal/discovery" |
||||
|
|
||||
|
"github.com/urfave/cli/v2" |
||||
|
) |
||||
|
|
||||
|
// 初始化配置
|
||||
|
func initConfig(c *cli.Context) error { |
||||
|
return config.G.SetConf(port) |
||||
|
} |
||||
|
|
||||
|
// 发送端
|
||||
|
func sendClient() error { |
||||
|
// 发送者角色
|
||||
|
go discovery.Send(discovery.Sender) |
||||
|
//udp 监听指定路径下的文件
|
||||
|
go discovery.Listen(discovery.Receiver, config.G.FilePath) |
||||
|
|
||||
|
url := fmt.Sprintf("http://%s:%s", config.G.LocalIP, config.G.Port) |
||||
|
fmt.Printf("Server address: %s\n", url) |
||||
|
|
||||
|
http.HandleFunc("/", handler.SendHandler) |
||||
|
// 加载静态资源
|
||||
|
http.Handle("/static/", http.StripPrefix("/static/", |
||||
|
http.FileServer(http.FS(web.StaticFs)))) |
||||
|
http.HandleFunc("/download/", handler.FileServerHandler) |
||||
|
// 下载压缩包
|
||||
|
http.HandleFunc("/dlzip", handler.Downzip) |
||||
|
// udp 传送文件
|
||||
|
http.HandleFunc("/sendZip", handler.SendZip) |
||||
|
// 已经打包的zip存放位置
|
||||
|
http.HandleFunc("/files", handler.Dfiles) |
||||
|
|
||||
|
fmt.Println("send file to server...") |
||||
|
|
||||
|
return http.ListenAndServe(config.G.WildcardAddress, nil) |
||||
|
} |
||||
|
|
||||
|
// 入口函数
|
||||
|
func main() { |
||||
|
app := &cli.App{ |
||||
|
Name: "fssc", |
||||
|
Usage: "file transfer tool", |
||||
|
UsageText: "fssc [global options] [filename|foldername]", |
||||
|
Description: "fssc is a simple command-line tool send file/folder", |
||||
|
Flags: []cli.Flag{ |
||||
|
&cli.StringFlag{ |
||||
|
Name: "port", |
||||
|
Value: "9099", |
||||
|
Usage: "server port", |
||||
|
Aliases: []string{"p"}, |
||||
|
Destination: &port, |
||||
|
}, |
||||
|
&cli.BoolFlag{ |
||||
|
Name: "version", |
||||
|
Aliases: []string{"v"}, |
||||
|
Usage: "print the version", |
||||
|
}, |
||||
|
}, |
||||
|
Action: func(c *cli.Context) error { |
||||
|
|
||||
|
if c.NArg() > 0 { |
||||
|
currentPath := filepath.ToSlash(c.Args().Get(0)) |
||||
|
config.G.FilePath = currentPath |
||||
|
return sendClient() |
||||
|
} |
||||
|
//return receiveClient()
|
||||
|
}, |
||||
|
Before: initConfig, |
||||
|
} |
||||
|
err := app.Run(os.Args) |
||||
|
if err != nil { |
||||
|
fmt.Println(err.Error()) |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue