package transfer import ( "context" "fmt" "io" "log" "m3u8-downloader/pkg/nas" "os" "os/exec" "path/filepath" "strings" ) type NASTransfer struct { config nas.NASConfig connected bool } func NewNASTransfer(config nas.NASConfig) *NASTransfer { nt := &NASTransfer{ config: config, } // Establish network connection with credentials before accessing the path if err := nt.establishConnection(); err != nil { log.Fatalf("Failed to establish network connection to %s: %v", nt.config.Path, err) } err := nt.ensureDirectoryExists(nt.config.Path) if err != nil { log.Fatalf("Failed to create directory %s: %v", nt.config.Path, err) } return nt } func (nt *NASTransfer) TransferFile(ctx context.Context, item *TransferItem) error { destPath := filepath.Join(nt.config.Path, item.DestinationPath) destDir := filepath.Dir(destPath) if err := nt.ensureDirectoryExists(destDir); err != nil { return fmt.Errorf("Failed to create directory %s: %w", destDir, err) } transferCtx, cancel := context.WithTimeout(ctx, nt.config.Timeout) defer cancel() if err := nt.copyFile(transferCtx, item.SourcePath, destPath); err != nil { return fmt.Errorf("Failed to copy file %s to %s: %w", item.SourcePath, destPath, err) } if nt.config.VerifySize { if err := nt.VerifyTransfer(item.SourcePath, destPath); err != nil { os.Remove(destPath) return fmt.Errorf("Failed to verify transfer: %w", err) } } log.Printf("File transfer completed: %s -> %s", item.SourcePath, destPath) return nil } func (nt *NASTransfer) copyFile(ctx context.Context, srcPath, destPath string) error { src, err := os.Open(srcPath) if err != nil { return fmt.Errorf("Failed to open source file: %w", err) } defer src.Close() dest, err := os.Create(destPath) if err != nil { return fmt.Errorf("Failed to create destination file: %w", err) } defer dest.Close() done := make(chan error, 1) go func() { _, err := io.Copy(dest, src) done <- err }() select { case <-ctx.Done(): return ctx.Err() case err := <-done: if err != nil { return err } return dest.Sync() } } func (nt *NASTransfer) VerifyTransfer(srcPath, destPath string) error { srcInfo, err := os.Stat(srcPath) if err != nil { return fmt.Errorf("Failed to stat source file: %w", err) } destInfo, err := os.Stat(destPath) if err != nil { return fmt.Errorf("Failed to stat destination file: %w", err) } if srcInfo.Size() != destInfo.Size() { return fmt.Errorf("size mismatch: source=%d, dest=%d", srcInfo.Size(), destInfo.Size()) } return nil } func (nt *NASTransfer) ensureDirectoryExists(path string) error { if err := os.MkdirAll(path, 0755); err != nil { return fmt.Errorf("Failed to create directory: %w", err) } return nil } func (nt *NASTransfer) establishConnection() error { // Extract the network path (\\server\share) from the full path networkPath := nt.extractNetworkPath(nt.config.Path) if networkPath == "" { // Local path, no authentication needed return nil } log.Printf("Establishing network connection to %s with user %s", networkPath, nt.config.Username) // Use Windows net use command to establish authenticated connection var cmd *exec.Cmd if nt.config.Username != "" && nt.config.Password != "" { cmd = exec.Command("net", "use", networkPath, nt.config.Password, "/user:"+nt.config.Username, "/persistent:no") } else { cmd = exec.Command("net", "use", networkPath, "/persistent:no") } output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to establish network connection: %w\nOutput: %s", err, string(output)) } log.Printf("Network connection established successfully") return nil } func (nt *NASTransfer) extractNetworkPath(fullPath string) string { // Extract \\server\share from paths like \\server\share\folder\subfolder if !strings.HasPrefix(fullPath, "\\\\") { return "" // Not a UNC path } parts := strings.Split(fullPath[2:], "\\") // Remove leading \\ if len(parts) < 2 { return "" // Invalid UNC path } return "\\\\" + parts[0] + "\\" + parts[1] } func (nt *NASTransfer) TestConnection() error { testFile := filepath.Join(nt.config.Path, ".connection_test") f, err := os.Create(testFile) if err != nil { return fmt.Errorf("Failed to create test file: %w", err) } f.Close() os.Remove(testFile) nt.connected = true log.Printf("Connected to NAS at %s", nt.config.Path) return nil } func (nt *NASTransfer) IsConnected() bool { return nt.connected } // Disconnect removes the network connection func (nt *NASTransfer) Disconnect() error { networkPath := nt.extractNetworkPath(nt.config.Path) if networkPath == "" { return nil // Local path, nothing to disconnect } cmd := exec.Command("net", "use", networkPath, "/delete") output, err := cmd.CombinedOutput() if err != nil { log.Printf("Warning: failed to disconnect from %s: %v\nOutput: %s", networkPath, err, string(output)) // Don't return error since this is cleanup } else { log.Printf("Disconnected from network path: %s", networkPath) } nt.connected = false return nil } // FileExists checks if a file already exists on the NAS and optionally verifies size func (nt *NASTransfer) FileExists(destinationPath string, expectedSize int64) (bool, error) { fullDestPath := filepath.Join(nt.config.Path, destinationPath) destInfo, err := os.Stat(fullDestPath) if err != nil { if os.IsNotExist(err) { return false, nil // File doesn't exist, no error } return false, fmt.Errorf("failed to stat NAS file: %w", err) } // File exists, check size if expected size is provided if expectedSize > 0 && destInfo.Size() != expectedSize { log.Printf("NAS file size mismatch for %s: expected=%d, actual=%d", fullDestPath, expectedSize, destInfo.Size()) return false, nil // File exists but wrong size, treat as not existing } return true, nil } // GetFileSize returns the size of a file on the NAS func (nt *NASTransfer) GetFileSize(destinationPath string) (int64, error) { fullDestPath := filepath.Join(nt.config.Path, destinationPath) destInfo, err := os.Stat(fullDestPath) if err != nil { return 0, fmt.Errorf("failed to stat NAS file: %w", err) } return destInfo.Size(), nil }