// MIT License // // Copyright (c) 2024 godoos.com // Email: xpbb@qq.com // GitHub: github.com/phpk/godoos // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. package localchat import ( "encoding/binary" "encoding/json" "fmt" "godo/libs" "io" "log" "net" "net/http" "os" "path/filepath" "strconv" "strings" "sync" "time" ) const ( GlobalfileSize = 768 // 每个数据包的大小 ) type FileChunk struct { ChunkIndex int `json:"chunk_index"` Data []byte `json:"data"` Checksum uint32 `json:"checksum"` Timestamp time.Time `json:"timestamp"` Filename string `json:"filename"` Filesize int64 `json:"filesize"` } func HandlerApplySendFile(w http.ResponseWriter, r *http.Request) { var msg UdpMessage decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&msg); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } defer r.Body.Close() hostname, err := os.Hostname() if err != nil { libs.ErrorMsg(w, "HandleMessage error") return } msg.Hostname = hostname msg.Time = time.Now() msg.Type = "fileSending" msg.Message = "" SendToIP(msg) libs.SuccessMsg(w, nil, "请求文件发送成功") } func HandlerAccessFile(w http.ResponseWriter, r *http.Request) { var msg UdpMessage decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&msg); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } defer r.Body.Close() hostname, err := os.Hostname() if err != nil { libs.ErrorMsg(w, "HandleMessage error") return } msg.Hostname = hostname msg.Time = time.Now() msg.Type = "fileAccessed" msg.Message = "" SendToIP(msg) libs.SuccessMsg(w, nil, "请求文件发送成功") } func HandlerSendFile(msg UdpMessage) { toIp := msg.IP hostname, err := os.Hostname() if err != nil { log.Printf("HandleMessage error: %v", err) return } msg.Hostname = hostname msg.Time = time.Now() msg.Type = "file" basePath, err := libs.GetOsDir() if err != nil { log.Printf("GetOsDir error: %v", err) return } paths, ok := msg.Message.([]string) if !ok { log.Printf("invalid message type") return } for _, p := range paths { filePath := filepath.Join(basePath, p) // 处理单个文件或整个文件夹 if fileInfo, err := os.Stat(filePath); err == nil { if fileInfo.IsDir() { handleDirectory(filePath, toIp, msg) } else { handleFile(filePath, toIp, msg) } } else { continue } } msg.Type = "fileSended" msg.Message = "" msg.Time = time.Now() SendToIP(msg) } func handleFile(filePath string, toIp string, message UdpMessage) { // 打开文件 file, err := os.Open(filePath) if err != nil { log.Fatalf("Failed to open file: %v", err) } defer file.Close() // 获取文件大小 fileInfo, err := file.Stat() if err != nil { log.Fatalf("Failed to get file info: %v", err) } fileSize := fileInfo.Size() // 计算需要发送的数据包数量 numChunks := (fileSize + GlobalfileSize - 1) / GlobalfileSize // 发送文件 SendFile(file, int(numChunks), toIp, fileSize, message) } func handleDirectory(dirPath string, toIp string, message UdpMessage) { err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { handleFile(path, toIp, message) } return nil }) if err != nil { log.Fatalf("Failed to walk directory: %v", err) } } func SendFile(file *os.File, numChunks int, toIp string, fSize int64, message UdpMessage) { var wg sync.WaitGroup // 逐块读取文件并发送 for i := 0; i < numChunks; i++ { wg.Add(1) go func(index int) { defer wg.Done() idStr := message.Type + "@" + message.Hostname + "@" + filepath.Base(file.Name()) + "@" + fmt.Sprintf("%d", fSize) + "@" //fileLen := GlobalfileSize - len(idStr) chunkData := make([]byte, GlobalfileSize) n, err := file.Read(chunkData[:]) if err != nil && err != io.EOF { log.Fatalf("Failed to read file chunk: %v", err) } sendBinaryData(idStr, chunkData[:n], toIp) fmt.Printf("发送文件块 %d 到 %s 成功\n", index, toIp) }(i) } wg.Wait() } func sendBinaryData(idStr string, data []byte, toIp string) { port := "56780" addr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%s", toIp, port)) if err != nil { log.Fatalf("Failed to resolve UDP address: %v", err) } conn, err := net.DialUDP("udp4", nil, addr) if err != nil { log.Fatalf("Failed to dial UDP address: %v", err) } defer conn.Close() // 添加长度前缀 identifier := []byte(idStr) lengthPrefix := make([]byte, 4) binary.BigEndian.PutUint32(lengthPrefix, uint32(len(data))) // 发送长度前缀和数据 _, err = conn.Write(append(append(identifier, lengthPrefix...), data...)) if err != nil { log.Printf("Failed to write data: %v", err) } } var fileLocks = make(map[string]*sync.Mutex) func ReceiveFiles(parts []string) (string, error) { fileName := parts[2] fileSize, err := strconv.ParseInt(parts[3], 10, 64) if err != nil { return "", fmt.Errorf("failed to parse file size: %v", err) } // 创建接收文件的目录 baseDir, err := libs.GetOsDir() if err != nil { log.Printf("Failed to get OS directory: %v", err) return "", fmt.Errorf("failed to get OS directory") } resPath := filepath.Join("C", "Users", "Reciv", time.Now().Format("2006-01-02")) receiveDir := filepath.Join(baseDir, resPath) if !libs.PathExists(receiveDir) { err := os.MkdirAll(receiveDir, 0755) if err != nil { log.Printf("Failed to create receive directory: %v", err) return "", fmt.Errorf("failed to create receive directory") } } // 确定文件路径 filePath := filepath.Join(receiveDir, fileName) // 如果文件不存在,则创建新文件 if _, err := os.Stat(filePath); os.IsNotExist(err) { file, err := os.Create(filePath) if err != nil { log.Printf("Failed to create file: %v", err) return "", fmt.Errorf("failed to create file") } defer file.Close() } // 锁定文件 lock, ok := fileLocks[fileName] if !ok { lock = &sync.Mutex{} fileLocks[fileName] = lock } lock.Lock() defer lock.Unlock() file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Printf("Failed to open file: %v", err) return "", fmt.Errorf("failed to open file") } defer file.Close() // 提取实际数据 data := strings.Join(parts[4:], "") if len(data) < 1 { return "", fmt.Errorf("empty data") } n, err := file.Write([]byte(data)) if err != nil { log.Printf("Failed to write data to file: %v", err) return "", fmt.Errorf("failed to write data to file") } if n != len(data) { log.Printf("Incomplete write: wrote %d bytes, expected %d bytes", n, len(data)) return "", fmt.Errorf("incomplete write") } fileInfo, err := os.Stat(filePath) if err != nil { log.Printf("Failed to stat file: %v", err) return "", fmt.Errorf("failed to stat file") } if fileInfo.Size() == fileSize { fmt.Println("文件接收完成且大小一致") return filePath, nil } else { fmt.Printf("文件大小不一致,发送大小为%d,接收大小为%d\n", fileSize, fileInfo.Size()) return "", fmt.Errorf("file size mismatch") } }