package grsync import ( "fmt" "io" "os" "os/exec" "strconv" "strings" ) // Rsync is wrapper under rsync type Rsync struct { Source string Destination string cmd *exec.Cmd } // RsyncOptions for rsync type RsyncOptions struct { // Verbose increase verbosity Verbose bool // Quiet suppress non-error messages Quiet bool // Checksum skip based on checksum, not mod-time & size Checksum bool // Archive is archive mode; equals -rlptgoD (no -H,-A,-X) Archive bool // Recurse into directories Recursive bool // Relative option to use relative path names Relative bool // NoImpliedDirs don't send implied dirs with --relative NoImpliedDirs bool // Update skip files that are newer on the receiver Update bool // Inplace update destination files in-place Inplace bool // Append data onto shorter files Append bool // AppendVerify --append w/old data in file checksum AppendVerify bool // Dirs transfer directories without recursing Dirs bool // Links copy symlinks as symlinks Links bool // CopyLinks transform symlink into referent file/dir CopyLinks bool // CopyUnsafeLinks only "unsafe" symlinks are transformed CopyUnsafeLinks bool // SafeLinks ignore symlinks that point outside the tree SafeLinks bool // CopyDirLinks transform symlink to dir into referent dir CopyDirLinks bool // KeepDirLinks treat symlinked dir on receiver as dir KeepDirLinks bool // HardLinks preserve hard links HardLinks bool // Perms preserve permissions Perms bool // Executability preserve executability Executability bool // CHMOD affect file and/or directory permissions CHMOD os.FileMode // Acls preserve ACLs (implies -p) ACLs bool // XAttrs preserve extended attributes XAttrs bool // Owner preserve owner (super-user only) Owner bool // Group preserve group Group bool // Devices preserve device files (super-user only) Devices bool // Specials preserve special files Specials bool // Times preserve modification times Times bool // omit directories from --times OmitDirTimes bool // Super receiver attempts super-user activities Super bool // FakeSuper store/recover privileged attrs using xattrs FakeSuper bool // Sparce handle sparse files efficiently Sparse bool // DryRun perform a trial run with no changes made DryRun bool // WholeFile copy files whole (w/o delta-xfer algorithm) WholeFile bool // OneFileSystem don't cross filesystem boundaries OneFileSystem bool // BlockSize block-size=SIZE force a fixed checksum block-size BlockSize int // Rsh -rsh=COMMAND specify the remote shell to use Rsh string // RsyncProgramm rsync-path=PROGRAM specify the rsync to run on remote machine RsyncProgramm string // Existing skip creating new files on receiver Existing bool // IgnoreExisting skip updating files that exist on receiver IgnoreExisting bool // RemoveSourceFiles sender removes synchronized files (non-dir) RemoveSourceFiles bool // Delete delete extraneous files from dest dirs Delete bool // DeleteBefore receiver deletes before transfer, not during DeleteBefore bool // DeleteDuring receiver deletes during the transfer DeleteDuring bool // DeleteDelay find deletions during, delete after DeleteDelay bool // DeleteAfter receiver deletes after transfer, not during DeleteAfter bool // DeleteExcluded also delete excluded files from dest dirs DeleteExcluded bool // IgnoreErrors delete even if there are I/O errors IgnoreErrors bool // Force deletion of dirs even if not empty Force bool // MaxDelete max-delete=NUM don't delete more than NUM files MaxDelete int // MaxSize max-size=SIZE don't transfer any file larger than SIZE MaxSize int // MinSize don't transfer any file smaller than SIZE MinSize int // Partial keep partially transferred files Partial bool // PartialDir partial-dir=DIR PartialDir string // DelayUpdates put all updated files into place at end DelayUpdates bool // PruneEmptyDirs prune empty directory chains from file-list PruneEmptyDirs bool // NumericIDs don't map uid/gid values by user/group name NumericIDs bool // Timeout timeout=SECONDS set I/O timeout in seconds Timeout int // Contimeout contimeout=SECONDS set daemon connection timeout in seconds Contimeout int // IgnoreTimes don't skip files that match size and time IgnoreTimes bool // SizeOnly skip files that match in size SizeOnly bool // ModifyWindow modify-window=NUM compare mod-times with reduced accuracy ModifyWindow bool // TempDir temp-dir=DIR create temporary files in directory DIR TempDir string // Fuzzy find similar file for basis if no dest file Fuzzy bool // CompareDest compare-dest=DIR also compare received files relative to DIR CompareDest string // CopyDest copy-dest=DIR ... and include copies of unchanged files CopyDest string // LinkDest link-dest=DIR hardlink to files in DIR when unchanged LinkDest string // Compress file data during the transfer Compress bool // CompressLevel explicitly set compression level CompressLevel int // SkipCompress skip-compress=LIST skip compressing files with suffix in LIST SkipCompress []string // CVSExclude auto-ignore files in the same way CVS does CVSExclude bool // Stats give some file-transfer stats Stats bool // HumanReadable output numbers in a human-readable format HumanReadable bool // Progress show progress during transfer Progress bool // 端口 Port int // 密钥文件 PasswordFile string // Info Info string // ipv4 IPv4 bool // ipv6 IPv6 bool } // StdoutPipe returns a pipe that will be connected to the command's // standard output when the command starts. func (r Rsync) StdoutPipe() (io.ReadCloser, error) { return r.cmd.StdoutPipe() } // StderrPipe returns a pipe that will be connected to the command's // standard error when the command starts. func (r Rsync) StderrPipe() (io.ReadCloser, error) { return r.cmd.StderrPipe() } // Run start rsync task func (r Rsync) Run() error { // 目标地址为 远程地址 则不进行文件夹创建 if !isRsyncPath(r.Destination) && !isExist(r.Destination) { if err := createDir(r.Destination); err != nil { return err } } if err := r.cmd.Start(); err != nil { return err } return r.cmd.Wait() } // NewRsync returns task with described options func NewRsync(source, destination string, options RsyncOptions) *Rsync { arguments := append(getArguments(options), source, destination) return &Rsync{ Source: source, Destination: destination, cmd: exec.Command("rsync", arguments...), } } func getArguments(options RsyncOptions) []string { arguments := []string{} if options.Verbose { arguments = append(arguments, "--verbose") } if options.Checksum { arguments = append(arguments, "--checksum") } if options.Quiet { arguments = append(arguments, "--quiet") } if options.Archive { arguments = append(arguments, "--archive") } if options.Recursive { arguments = append(arguments, "--recursive") } if options.Relative { arguments = append(arguments, "--relative") } if options.NoImpliedDirs { arguments = append(arguments, "--no-implied-dirs") } if options.Update { arguments = append(arguments, "--update") } if options.Inplace { arguments = append(arguments, "--inplace") } if options.Append { arguments = append(arguments, "--append") } if options.AppendVerify { arguments = append(arguments, "--append-verify") } if options.Dirs { arguments = append(arguments, "--dirs") } if options.Links { arguments = append(arguments, "--links") } if options.CopyLinks { arguments = append(arguments, "--copy-links") } if options.CopyUnsafeLinks { arguments = append(arguments, "--copy-unsafe-links") } if options.SafeLinks { arguments = append(arguments, "--safe-links") } if options.CopyDirLinks { arguments = append(arguments, "--copy-dir-links") } if options.KeepDirLinks { arguments = append(arguments, "--keep-dir-links") } if options.HardLinks { arguments = append(arguments, "--hard-links") } if options.Perms { arguments = append(arguments, "--perms") } if options.Executability { arguments = append(arguments, "--executability") } if options.ACLs { arguments = append(arguments, "--acls") } if options.XAttrs { arguments = append(arguments, "--xattrs") } if options.Owner { arguments = append(arguments, "--owner") } if options.Group { arguments = append(arguments, "--group") } if options.Devices { arguments = append(arguments, "--devices") } if options.Port > 0 { arguments = append(arguments, fmt.Sprintf("--port=%d", options.Port)) } if options.Specials { arguments = append(arguments, "--specials") } if options.Times { arguments = append(arguments, "--times") } if options.OmitDirTimes { arguments = append(arguments, "--omit-dir-times") } if options.Super { arguments = append(arguments, "--super") } if options.FakeSuper { arguments = append(arguments, "--fake-super") } if options.Sparse { arguments = append(arguments, "--sparse") } if options.DryRun { arguments = append(arguments, "--dry-run") } if options.WholeFile { arguments = append(arguments, "--whole-file") } if options.OneFileSystem { arguments = append(arguments, "--one-file-system") } if options.BlockSize > 0 { arguments = append(arguments, "--block-size", strconv.Itoa(options.BlockSize)) } if options.Rsh != "" { arguments = append(arguments, "--rsh", options.Rsh) } if options.RsyncProgramm != "" { arguments = append(arguments, "--rsync-programm", options.RsyncProgramm) } if options.Existing { arguments = append(arguments, "--existing") } if options.IgnoreExisting { arguments = append(arguments, "--ignore-existing") } if options.RemoveSourceFiles { arguments = append(arguments, "--remove-source-files") } if options.Delete { arguments = append(arguments, "--delete") } if options.DeleteBefore { arguments = append(arguments, "--delete-before") } if options.DeleteDuring { arguments = append(arguments, "--delete-during") } if options.DeleteDelay { arguments = append(arguments, "--delete-delay") } if options.DeleteAfter { arguments = append(arguments, "--delete-after") } if options.DeleteExcluded { arguments = append(arguments, "--delete-excluded") } if options.IgnoreErrors { arguments = append(arguments, "--ignore-errors") } if options.Force { arguments = append(arguments, "--force") } if options.MaxDelete > 0 { arguments = append(arguments, "--max-delete", strconv.Itoa(options.MaxDelete)) } if options.MaxSize > 0 { arguments = append(arguments, "--max-size", strconv.Itoa(options.MaxSize)) } if options.MinSize > 0 { arguments = append(arguments, "--min-size", strconv.Itoa(options.MinSize)) } if options.Partial { arguments = append(arguments, "--partial") } if options.PartialDir != "" { arguments = append(arguments, "--partial-dir", options.PartialDir) } if options.DelayUpdates { arguments = append(arguments, "--delay-updates") } if options.PruneEmptyDirs { arguments = append(arguments, "--prune-empty-dirs") } if options.NumericIDs { arguments = append(arguments, "--numeric-ids") } if options.Timeout > 0 { arguments = append(arguments, "--timeout", strconv.Itoa(options.Timeout)) } if options.Contimeout > 0 { arguments = append(arguments, "--contimeout", strconv.Itoa(options.Contimeout)) } if options.IgnoreTimes { arguments = append(arguments, "--ignore-times") } if options.SizeOnly { arguments = append(arguments, "--size-only") } if options.ModifyWindow { arguments = append(arguments, "--modify-window") } if options.TempDir != "" { arguments = append(arguments, "--temp-dir", options.TempDir) } if options.Fuzzy { arguments = append(arguments, "--fuzzy") } if options.CompareDest != "" { arguments = append(arguments, "--compare-dest", options.CompareDest) } if options.CopyDest != "" { arguments = append(arguments, "--copy-dest", options.CopyDest) } if options.LinkDest != "" { arguments = append(arguments, "--link-dest", options.LinkDest) } if options.Compress { arguments = append(arguments, "--compress") } if options.CompressLevel > 0 { arguments = append(arguments, "--compress-level", strconv.Itoa(options.CompressLevel)) } if len(options.SkipCompress) > 0 { arguments = append(arguments, "--skip-compress", strings.Join(options.SkipCompress, ",")) } if options.CVSExclude { arguments = append(arguments, "--cvs-exclude") } if options.Stats { arguments = append(arguments, "--stats") } if options.HumanReadable { arguments = append(arguments, "--human-readable") } if options.Progress { arguments = append(arguments, "--progress") } if options.IPv4 { arguments = append(arguments, "--ipv4") } if options.IPv6 { arguments = append(arguments, "--ipv6") } if options.Info != "" { arguments = append(arguments, "--info", options.Info) } if options.PasswordFile != "" { arguments = append(arguments, "--password-file", options.PasswordFile) } return arguments } func createDir(dir string) error { cmd := exec.Command("mkdir", "-p", dir) if err := cmd.Start(); err != nil { return err } return cmd.Wait() } func isExist(p string) bool { stat, err := os.Stat(p) return os.IsExist(err) && stat.IsDir() } // isRsyncPath 判断一个给定的字符串或路径是否格式是否符合rsync协议的要求 func isRsyncPath(path string) bool { if strings.HasPrefix(path, "rsync://") { return true } parts := strings.SplitN(path, ":", 2) if len(parts) == 2 && strings.Contains(parts[0], "@") { return true } return false }