diff --git a/godo/cmd/main.go b/godo/cmd/main.go index c5b594e..b0ea857 100644 --- a/godo/cmd/main.go +++ b/godo/cmd/main.go @@ -107,6 +107,7 @@ func OsStart() { localchatRouter.HandleFunc("/cannelfile", localchat.HandlerCannelFile).Methods(http.MethodPost) localchatRouter.HandleFunc("/accessfile", localchat.HandlerAccessFile).Methods(http.MethodPost) localchatRouter.HandleFunc("/getfiles", localchat.HandleGetFiles).Methods(http.MethodPost) + localchatRouter.HandleFunc("/servefile", localchat.HandleServeFile).Methods(http.MethodGet) localchatRouter.HandleFunc("/sendimage", localchat.HandlerSendImg).Methods(http.MethodPost) localchatRouter.HandleFunc("/viewimage", localchat.HandleViewImg).Methods(http.MethodGet) localchatRouter.HandleFunc("/setting", localchat.HandleAddr).Methods(http.MethodPost) diff --git a/godo/localchat/filedown.go b/godo/localchat/filedown.go index 559a1d8..ae21ea0 100644 --- a/godo/localchat/filedown.go +++ b/godo/localchat/filedown.go @@ -71,7 +71,7 @@ func downloadFiles(msg UdpMessage) error { } // 处理响应中的文件 - if err := handleResponse(resp.Body, receiveDir); err != nil { + if err := handleResponse(resp.Body, receiveDir, msg.IP); err != nil { log.Fatalf("Failed to handle response: %v", err) } @@ -79,105 +79,63 @@ func downloadFiles(msg UdpMessage) error { return nil } -func handleResponse(reader io.Reader, saveDir string) error { +func handleResponse(reader io.Reader, saveDir string, ip string) error { body, err := io.ReadAll(reader) if err != nil { return fmt.Errorf("failed to read response body: %v", err) } log.Printf("Received file list: %v", string(body)) - var fileList FileList + // 解析文件列表 + var fileList []FileItem if err := json.Unmarshal(body, &fileList); err != nil { return fmt.Errorf("failed to unmarshal file list: %v", err) } - log.Printf("Received file list: %v", fileList.Files) - for _, filePath := range fileList.Files { - err := saveFileOrFolder(filePath, saveDir) - if err != nil { - return fmt.Errorf("failed to save file or folder: %v", err) - } - } - - return nil -} - -func saveFileOrFolder(filePath string, saveDir string) error { - relativePath := filePath - localFilePath := filepath.Join(saveDir, relativePath) + log.Printf("Received file list: %v", fileList) - // 检查是否为文件夹 - if _, err := os.Stat(localFilePath); os.IsNotExist(err) { - // 如果不存在,则创建文件夹 - err := os.MkdirAll(localFilePath, 0755) - if err != nil { + for _, file := range fileList { + checkpath := filepath.Join(saveDir, file.WritePath) + if err := os.MkdirAll(checkpath, 0755); err != nil { return fmt.Errorf("failed to create directory: %v", err) } - } else if fileInfo, err := os.Stat(localFilePath); err == nil && fileInfo.IsDir() { - // 如果是文件夹,则递归处理 - err := saveFolder(relativePath, saveDir) - if err != nil { - return fmt.Errorf("failed to save folder: %v", err) - } - } else { - // 如果是文件,则直接保存 - err := saveFile(localFilePath) - if err != nil { - return fmt.Errorf("failed to save file: %v", err) + if !file.IsDir { + if err := downloadFile(file.Path, checkpath, ip); err != nil { + return fmt.Errorf("failed to download file: %v", err) + } } } return nil } -func saveFolder(folderName string, saveDir string) error { - localFolderPath := filepath.Join(saveDir, folderName) - err := os.MkdirAll(localFolderPath, 0755) - if err != nil { - return fmt.Errorf("failed to create directory: %v", err) - } +// downloadFile 下载单个文件 +func downloadFile(filePath string, checkpath string, ip string) error { + url := fmt.Sprintf("http://%s:56780/localchat/servefile?path=%s", ip, filePath) - // 获取文件夹内容 - folderPath := filepath.Join(saveDir, folderName) - files, err := os.ReadDir(folderPath) + resp, err := http.Get(url) if err != nil { - return fmt.Errorf("failed to read directory: %v", err) + return fmt.Errorf("failed to download file: %v", err) } + defer resp.Body.Close() - for _, file := range files { - subPath := filepath.Join(folderName, file.Name()) - if file.IsDir() { - err := saveFolder(subPath, localFolderPath) - if err != nil { - return fmt.Errorf("failed to save folder: %v", err) - } - } else { - err := saveFile(filepath.Join(localFolderPath, file.Name())) - if err != nil { - return fmt.Errorf("failed to save file: %v", err) - } - } + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("unexpected status code: %v, body: %s", resp.StatusCode, body) } - return nil -} - -func saveFile(filePath string) error { - file, err := os.Open(filePath) - if err != nil { - return fmt.Errorf("failed to open file: %v", err) - } - defer file.Close() + // 保存文件 + fileName := filepath.Join(checkpath, filepath.Base(filePath)) - // 创建本地文件 - localFile, err := os.Create(filePath) + out, err := os.Create(fileName) if err != nil { return fmt.Errorf("failed to create file: %v", err) } - defer localFile.Close() + defer out.Close() - _, err = io.Copy(localFile, file) + _, err = io.Copy(out, resp.Body) if err != nil { - return fmt.Errorf("failed to copy file: %v", err) + return fmt.Errorf("failed to write file: %v", err) } + fmt.Printf("File downloaded: %s\n", fileName) return nil } diff --git a/godo/localchat/filemsg.go b/godo/localchat/filemsg.go index efb0312..2a55b12 100644 --- a/godo/localchat/filemsg.go +++ b/godo/localchat/filemsg.go @@ -1,3 +1,26 @@ +// 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 ( diff --git a/godo/localchat/getfiles.go b/godo/localchat/getfiles.go index 6df006f..8107c50 100644 --- a/godo/localchat/getfiles.go +++ b/godo/localchat/getfiles.go @@ -1,17 +1,45 @@ +// 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/json" "fmt" "godo/libs" + "io" "log" "net/http" "os" "path/filepath" ) -type FileList struct { - Files []string `json:"fileList"` +// FileItem 表示文件或文件夹 +type FileItem struct { + Path string `json:"path"` + IsDir bool `json:"isDir"` + Filename string `json:"filename"` + WritePath string `json:"writePath"` } func HandleGetFiles(w http.ResponseWriter, r *http.Request) { @@ -20,7 +48,7 @@ func HandleGetFiles(w http.ResponseWriter, r *http.Request) { return } - var fileList FileList + var fileList []string err := json.NewDecoder(r.Body).Decode(&fileList) if err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) @@ -32,18 +60,42 @@ func HandleGetFiles(w http.ResponseWriter, r *http.Request) { baseDir, err := libs.GetOsDir() if err != nil { log.Printf("Failed to get OS directory: %v", err) + http.Error(w, "Failed to get OS directory", http.StatusInternalServerError) return } // 用于存储文件列表 - var files []string + var files []FileItem - for _, filePath := range fileList.Files { + for _, filePath := range fileList { fp := filepath.Join(baseDir, filePath) - if err := serveDirectory(w, r, fp, &files); err != nil { - http.Error(w, fmt.Sprintf("Failed to serve directory: %v", err), http.StatusInternalServerError) + + fileInfo, err := os.Stat(fp) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to stat file: %v", err), http.StatusInternalServerError) return } + writePath := calculateWritePath(fp, baseDir) + if fileInfo.IsDir() { + files = append(files, FileItem{ + Path: fp, + IsDir: true, + Filename: filepath.Base(fp), + WritePath: writePath, + }) + if err := walkDirectory(fp, &files, writePath); err != nil { + http.Error(w, fmt.Sprintf("Failed to serve directory: %v", err), http.StatusInternalServerError) + return + } + } else { + + files = append(files, FileItem{ + Path: fp, + IsDir: false, + Filename: filepath.Base(fp), + WritePath: writePath, + }) + } } // 将文件列表编码为 JSON 并返回 @@ -56,43 +108,75 @@ func HandleGetFiles(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write(jsonData) } - -func serveDirectory(w http.ResponseWriter, r *http.Request, dirPath string, files *[]string) error { - filesInDir, err := os.ReadDir(dirPath) +func calculateWritePath(filePath, baseDir string) string { + relativePath, err := filepath.Rel(baseDir, filePath) if err != nil { - return fmt.Errorf("failed to read directory: %v", err) + log.Printf("Failed to calculate relative path: %v", err) + return "" } - - for _, f := range filesInDir { - filePath := filepath.Join(dirPath, f.Name()) - if f.IsDir() { - if err := serveDirectory(w, r, filePath, files); err != nil { - return err - } - } else { - *files = append(*files, filePath) + return filepath.Dir(relativePath) +} +func walkDirectory(rootPath string, files *[]FileItem, writePath string) error { + return filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("failed to walk directory: %v", err) } - } - return nil + isDir := info.IsDir() + relativePath, err := filepath.Rel(rootPath, path) + if err != nil { + log.Printf("Failed to calculate relative path: %v", err) + return fmt.Errorf("Failed to calculate relative path") + } + currentWritePath := filepath.Join(writePath, filepath.Dir(relativePath)) + *files = append(*files, FileItem{ + Path: path, + IsDir: isDir, + Filename: filepath.Base(path), + WritePath: currentWritePath, + }) + + return nil + }) } -func ServeFile(w http.ResponseWriter, r *http.Request, filePath string) error { - file, err := os.Open(filePath) - if err != nil { - return fmt.Errorf("failed to open file: %v", err) +func HandleServeFile(w http.ResponseWriter, r *http.Request) { + // 从 URL 中获取 filePath 参数 + filePath := r.URL.Query().Get("path") + + if filePath == "" { + http.Error(w, "Missing filePath parameter", http.StatusBadRequest) + return } - defer file.Close() - fileInfo, err := file.Stat() + fileInfo, err := os.Stat(filePath) if err != nil { - return fmt.Errorf("failed to stat file: %v", err) + http.Error(w, fmt.Sprintf("Failed to stat file: %v", err), http.StatusInternalServerError) + return } if fileInfo.IsDir() { - return serveDirectory(w, r, filePath, nil) + http.Error(w, "Cannot download a directory", http.StatusBadRequest) + return + } + + // 打开文件 + file, err := os.Open(filePath) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to open file: %v", err), http.StatusInternalServerError) + return } + defer file.Close() + + // 设置响应头 + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileInfo.Name())) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size())) - http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file) - return nil + // 复制文件内容到响应体 + _, err = io.Copy(w, file) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to copy file content: %v", err), http.StatusInternalServerError) + return + } }