diff options
Diffstat (limited to 'internal/transfer')
-rw-r--r-- | internal/transfer/transfer.go | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/internal/transfer/transfer.go b/internal/transfer/transfer.go new file mode 100644 index 0000000..a57b073 --- /dev/null +++ b/internal/transfer/transfer.go @@ -0,0 +1,126 @@ +package transfer + +import ( + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + + "gscp/internal/config" +) + +// GetDiffFiles computes which files exist on remote but not on local +func GetDiffFiles(remoteFiles []string, localFiles map[string]bool) []string { + var diffFiles []string + + for _, remoteFile := range remoteFiles { + if !localFiles[remoteFile] { + diffFiles = append(diffFiles, remoteFile) + } + } + + return diffFiles +} + +// CopyFilesInParallel copies multiple files in parallel from remote to local +func CopyFilesInParallel(files []string, config config.Configuration) int { + if len(files) == 0 { + log.Println("No files to copy") + return 0 + } + + var wg sync.WaitGroup + sem := make(chan struct{}, config.Parallelism) + + totalFiles := len(files) + copiedFiles := 0 + var mu sync.Mutex + + for _, file := range files { + wg.Add(1) + go func(file string) { + defer wg.Done() + + // Acquire semaphore + sem <- struct{}{} + defer func() { <-sem }() + + if err := CopyFile(file, config); err != nil { + log.Printf("Error copying file %s: %v", file, err) + } else { + mu.Lock() + copiedFiles++ + if config.Verbose && copiedFiles%10 == 0 { + log.Printf("Progress: %d/%d files copied (%.2f%%)", + copiedFiles, totalFiles, float64(copiedFiles)/float64(totalFiles)*100) + } + mu.Unlock() + } + }(file) + } + + wg.Wait() + return copiedFiles +} + +// CopyFile copies a single file from remote to local +func CopyFile(file string, config config.Configuration) error { + // Create lock file path by replacing slashes with underscores + lockFileName := strings.ReplaceAll(file, "/", "_") + ".lock" + lockFilePath := filepath.Join(config.LockDir, lockFileName) + + // Try to create and lock the file + lockFile, err := os.OpenFile(lockFilePath, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("cannot create lock file: %v", err) + } + defer lockFile.Close() + + // Create destination directory + destDir := filepath.Join(config.Dest, filepath.Dir(file)) + if err := os.MkdirAll(destDir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %v", destDir, err) + } + + // Check if file already exists in destination + destFile := filepath.Join(config.Dest, file) + if _, err := os.Stat(destFile); err == nil { + // File already exists, skip + return nil + } + + // Construct scp command + src := fmt.Sprintf("%s@%s:%s/%s", config.RemoteUser, config.RemoteHost, config.RemotePath, file) + args := []string{ + "-q", // Quiet mode + "-c", config.CipherOption, + src, + destFile, + } + + if config.Verbose { + log.Printf("Copying file: %s", file) + } + + cmd := exec.Command("scp", args...) + + // Capture stdout and stderr + var stdoutBuf, stderrBuf strings.Builder + cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) + cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) + + // Execute command + err = cmd.Run() + if err != nil { + return fmt.Errorf("scp failed: %v, stderr: %s", err, stderrBuf.String()) + } + + // Clean up lock file on success + os.Remove(lockFilePath) + + return nil +} |