206 lines
5.2 KiB
Go
206 lines
5.2 KiB
Go
package nas
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type NASService struct {
|
|
Config NASConfig
|
|
connected bool
|
|
}
|
|
|
|
func NewNASService(config NASConfig) *NASService {
|
|
nt := &NASService{
|
|
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 *NASService) 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 *NASService) 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 *NASService) 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 *NASService) 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 *NASService) 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 *NASService) 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 *NASService) IsConnected() bool {
|
|
return nt.connected
|
|
}
|
|
|
|
// Disconnect removes the network connection
|
|
func (nt *NASService) 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 *NASService) 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 *NASService) 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
|
|
}
|