diff --git a/fsv2/README.md b/fsv2/README.md
index 968ca08..0c65713 100644
--- a/fsv2/README.md
+++ b/fsv2/README.md
@@ -1,2 +1,7 @@
## 文件系统 第二版本
+
+软件用于客户端,用于处理更新文件
+
+被动更新模式
+接收文件 --》查找文件并备份 --》 文件覆盖
\ No newline at end of file
diff --git a/fsv2/config/config.go b/fsv2/config/config.go
new file mode 100644
index 0000000..77214ab
--- /dev/null
+++ b/fsv2/config/config.go
@@ -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
+ }
+
+}
diff --git a/fsv2/go.mod b/fsv2/go.mod
index 07bbebe..0cfa67a 100644
--- a/fsv2/go.mod
+++ b/fsv2/go.mod
@@ -3,6 +3,17 @@ module xtcfs
go 1.22.1
require (
- github.com/arl/statsviz v0.6.0
- github.com/gorilla/websocket v1.5.3
+ github.com/chyok/st v0.0.0-20240414083746-2b61e9e205ae
+ github.com/schollz/progressbar/v3 v3.14.2
+ github.com/urfave/cli/v2 v2.27.2
+)
+
+require (
+ github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
+ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
+ golang.org/x/sys v0.19.0 // indirect
+ golang.org/x/term v0.19.0 // indirect
)
diff --git a/fsv2/go.sum b/fsv2/go.sum
index 7d3dee2..19b970e 100644
--- a/fsv2/go.sum
+++ b/fsv2/go.sum
@@ -1,10 +1,33 @@
-github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE=
-github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s=
-github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
-github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
-github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+github.com/chyok/st v0.0.0-20240414083746-2b61e9e205ae h1:h52yw9f/+Q4Mdyz79m7cnl4HTqY2VEo93eXkqSmiB7Q=
+github.com/chyok/st v0.0.0-20240414083746-2b61e9e205ae/go.mod h1:HPBtM/h5CwXveOvIpc3nWE0yO+KNsI/2wqw4iSYOZ/A=
+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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
+github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+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/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks=
+github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+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=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
diff --git a/fsv2/handler/handler.go b/fsv2/handler/handler.go
new file mode 100644
index 0000000..3dfab0e
--- /dev/null
+++ b/fsv2/handler/handler.go
@@ -0,0 +1,387 @@
+package handler
+
+import (
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "io"
+ "mime"
+ "net"
+ "path"
+ "strings"
+ "time"
+
+ "net/http"
+ "net/rpc"
+ "os"
+ "path/filepath"
+
+ "xtcfs/config"
+ "xtcfs/transfer"
+ "xtcfs/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, "%s
\n", url, url)
+ fmt.Fprintf(w, "RPC execute result :%d
\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
+ IsDir bool
+ FileName string
+ DownloadPath string
+ UrlPath string
+ Files []os.DirEntry
+ }{
+ DeviceName: config.G.DeviceName,
+ 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)
+ }
+
+ // 文件列表模板
+ 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)
+ }
+}
diff --git a/fsv2/main.go b/fsv2/main.go
index 4ae5e5f..8cfdce4 100644
--- a/fsv2/main.go
+++ b/fsv2/main.go
@@ -1,73 +1,77 @@
package main
import (
- "math/rand"
+ "fmt"
"net/http"
- "strconv"
- "time"
+ "os"
- "github.com/arl/statsviz"
- "github.com/gorilla/websocket"
-)
-
-var upgrader = websocket.Upgrader{
- CheckOrigin: func(r *http.Request) bool {
- return true // 允许跨域请求
- },
-}
+ "xtcfs/config"
+ "xtcfs/handler"
-func handleConnections(w http.ResponseWriter, r *http.Request) {
- // 将HTTP连接升级为WebSocket连接
- conn, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- println("Failed to set up WebSocket:", err)
- return
- }
- defer conn.Close()
+ "github.com/urfave/cli/v2"
+)
- // 这里可以添加代码来处理实时状态更新
- // 例如,可以循环接收客户端发送的消息,并发送给所有连接的客户端
- for {
- // 接收客户端消息
- messageType, p, err := conn.ReadMessage()
- if err != nil {
- println("Error reading message:", err)
- return
- }
- println("Received message:", string(p))
+var (
+ port string
+)
- // 将接收到的消息发送给所有客户端
- err = conn.WriteMessage(messageType, p)
- if err != nil {
- println("Error sending message:", err)
- return
- }
- }
+// 初始化配置
+func initConfig(c *cli.Context) error {
+ return config.G.SetConf(port)
}
-func work() {
- // Generate some allocations
- m := map[string][]byte{}
-
- for {
- b := make([]byte, 512+rand.Intn(16*1024))
- m[strconv.Itoa(len(m)%(10*100))] = b
+// 接收端 管理上传
+func receiveClient() error {
+ //接收者角色
- if len(m)%(10*100) == 0 {
- m = make(map[string][]byte)
- }
+ // 注释上面的局域网广播
+ fmt.Println("xtfs run as receive role...")
+ // 显示状态等
+ // http.HandleFunc("/", handler.ReceiveStatus)
+ http.HandleFunc("/", handler.ReceiveHandler)
+ // http.Handle("/static/", http.StripPrefix("/static/",
+ // http.FileServer(http.FS(web.StaticFs))))
- time.Sleep(10 * time.Millisecond)
- }
+ fmt.Println("Waiting for receive...")
+ return http.ListenAndServe(config.G.WildcardAddress, nil)
}
+// 入口函数
+/**
+* 保留接收的功能,用来做接收端
+**/
func main() {
- statsviz.RegisterDefault()
- go work()
- // http.ListenAndServe(":8080", nil)
- http.HandleFunc("/ws", handleConnections)
- err := http.ListenAndServe(":8080", nil)
+ app := &cli.App{
+ Name: "xtfs",
+ Usage: "simple file transfer tool",
+ UsageText: "xtfs [global options] [filename|foldername]",
+ Description: "xtfs is a simple command-line tool for fast local file/folder sharing",
+ 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 {
- println("ListenAndServe:", err)
+ fmt.Println(err.Error())
}
}
diff --git a/fsv2/fscore/receive.go b/fsv2/transfer/receive.go
similarity index 100%
rename from fsv2/fscore/receive.go
rename to fsv2/transfer/receive.go
diff --git a/fsv2/transfer/send.go b/fsv2/transfer/send.go
new file mode 100644
index 0000000..c14b3e3
--- /dev/null
+++ b/fsv2/transfer/send.go
@@ -0,0 +1,119 @@
+package transfer
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "xtcfs/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
+}
diff --git a/fsv2/util/util.go b/fsv2/util/util.go
new file mode 100644
index 0000000..2ab5699
--- /dev/null
+++ b/fsv2/util/util.go
@@ -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
+ })
+}