pax_global_header00006660000000000000000000000064135143535230014516gustar00rootroot0000000000000052 comment=0414179bde44a9b7df26b1ac05a7275a95dcd8c2 fsync-0.9.0/000077500000000000000000000000001351435352300126465ustar00rootroot00000000000000fsync-0.9.0/.gitignore000066400000000000000000000004061351435352300146360ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *~fsync-0.9.0/LICENSE000066400000000000000000000020451351435352300136540ustar00rootroot00000000000000Copyright (C) 2012 Mostafa Hajizadeh 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. fsync-0.9.0/README.md000066400000000000000000000002041351435352300141210ustar00rootroot00000000000000Package `fsync` keeps files and directories in sync. Read the documentation on [GoDoc](http://godoc.org/github.com/mostafah/fsync). fsync-0.9.0/fsync.go000066400000000000000000000176301351435352300143260ustar00rootroot00000000000000// Copyright (C) 2012 Mostafa Hajizadeh // Copyright (C) 2014-5 Steve Francia // package fsync keeps two files or directories in sync. // // err := fsync.Sync("~/dst", ".") // // After the above code, if err is nil, every file and directory in the current // directory is copied to ~/dst and has the same permissions. Consequent calls // will only copy changed or new files. // // SyncTo is a helper function which helps you sync a groups of files or // directories into a single destination. For instance, calling // // SyncTo("public", "build/app.js", "build/app.css", "images", "fonts") // // is equivalent to calling // // Sync("public/app.js", "build/app.js") // Sync("public/app.css", "build/app.css") // Sync("public/images", "images") // Sync("public/fonts", "fonts") // // Actually, this is how SyncTo is implemented: consequent calls to Sync. // // By default, sync code ignores extra files in the destination that don’t have // identicals in the source. Setting Delete field of a Syncer to true changes // this behavior and deletes these extra files. package fsync import ( "bytes" "errors" "io" "os" "path/filepath" "runtime" "github.com/spf13/afero" ) var ( ErrFileOverDir = errors.New( "fsync: trying to overwrite a non-empty directory with a file") ) // Sync copies files and directories inside src into dst. func Sync(dst, src string) error { return NewSyncer().Sync(dst, src) } // SyncTo syncs srcs files and directories into to directory. func SyncTo(to string, srcs ...string) error { return NewSyncer().SyncTo(to, srcs...) } // Type Syncer provides functions for syncing files. type Syncer struct { // Set this to true to delete files in the destination that don't exist // in the source. Delete bool // To allow certain files to remain in the destination, implement this function. // Return true to skip file, false to delete. DeleteFilter func(f os.FileInfo) bool // By default, modification times are synced. This can be turned off by // setting this to true. NoTimes bool // NoChmod disables permission mode syncing. NoChmod bool // Implement this function to skip Chmod syncing for only certain files // or directories. Return true to skip Chmod. ChmodFilter func(dst, src os.FileInfo) bool // TODO add options for not checking content for equality SrcFs afero.Fs DestFs afero.Fs } // NewSyncer creates a new instance of Syncer with default options. func NewSyncer() *Syncer { s := Syncer{SrcFs: new(afero.OsFs), DestFs: new(afero.OsFs)} s.DeleteFilter = func(f os.FileInfo) bool { return false } return &s } // Sync copies files and directories inside src into dst. func (s *Syncer) Sync(dst, src string) error { // make sure src exists if _, err := s.SrcFs.Stat(src); err != nil { return err } // return error instead of replacing a non-empty directory with a file if b, err := s.checkDir(dst, src); err != nil { return err } else if b { return ErrFileOverDir } return s.syncRecover(dst, src) } // SyncTo syncs srcs files or directories into to directory. func (s *Syncer) SyncTo(to string, srcs ...string) error { for _, src := range srcs { dst := filepath.Join(to, filepath.Base(src)) if err := s.Sync(dst, src); err != nil { return err } } return nil } // syncRecover handles errors and calls sync func (s *Syncer) syncRecover(dst, src string) (err error) { defer func() { if r := recover(); r != nil { if _, ok := r.(runtime.Error); ok { panic(r) } err = r.(error) } }() s.sync(dst, src) return nil } // sync updates dst to match with src, handling both files and directories. func (s *Syncer) sync(dst, src string) { // sync permissions and modification times after handling content defer s.syncstats(dst, src) // read files info dstat, err := s.DestFs.Stat(dst) if err != nil && !os.IsNotExist(err) { panic(err) } sstat, err := s.SrcFs.Stat(src) if err != nil && os.IsNotExist(err) { return // src was deleted before we could copy it } check(err) if !sstat.IsDir() { // src is a file // delete dst if its a directory if dstat != nil && dstat.IsDir() { check(s.DestFs.RemoveAll(dst)) } if !s.equal(dst, src) { // perform copy df, err := s.DestFs.Create(dst) check(err) defer df.Close() sf, err := s.SrcFs.Open(src) if os.IsNotExist(err) { return } check(err) defer sf.Close() _, err = io.Copy(df, sf) if os.IsNotExist(err) { return } check(err) } return } // src is a directory // make dst if necessary if dstat == nil { // dst does not exist; create directory check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later } else if !dstat.IsDir() { // dst is a file; remove and create directory check(s.DestFs.Remove(dst)) check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later } // go through sf files and sync them files, err := afero.ReadDir(s.SrcFs, src) if os.IsNotExist(err) { return } check(err) // make a map of filenames for quick lookup; used in deletion // deletion below m := make(map[string]bool, len(files)) for _, file := range files { dst2 := filepath.Join(dst, file.Name()) src2 := filepath.Join(src, file.Name()) s.sync(dst2, src2) m[file.Name()] = true } // delete files from dst that does not exist in src if s.Delete { files, err = afero.ReadDir(s.DestFs, dst) check(err) for _, file := range files { if !m[file.Name()] && !s.DeleteFilter(file) { check(s.DestFs.RemoveAll(filepath.Join(dst, file.Name()))) } } } } // syncstats makes sure dst has the same pemissions and modification time as src func (s *Syncer) syncstats(dst, src string) { // get file infos; return if not exist and panic if error dstat, err1 := s.DestFs.Stat(dst) sstat, err2 := s.SrcFs.Stat(src) if os.IsNotExist(err1) || os.IsNotExist(err2) { return } check(err1) check(err2) // update dst's permission bits noChmod := s.NoChmod if !noChmod && s.ChmodFilter != nil { noChmod = s.ChmodFilter(dstat, sstat) } if !noChmod { if dstat.Mode().Perm() != sstat.Mode().Perm() { check(s.DestFs.Chmod(dst, sstat.Mode().Perm())) } } // update dst's modification time if !s.NoTimes { if !dstat.ModTime().Equal(sstat.ModTime()) { err := s.DestFs.Chtimes(dst, sstat.ModTime(), sstat.ModTime()) check(err) } } } // equal returns true if both dst and src files are equal func (s *Syncer) equal(dst, src string) bool { // get file infos info1, err1 := s.DestFs.Stat(dst) info2, err2 := s.SrcFs.Stat(src) if os.IsNotExist(err1) || os.IsNotExist(err2) { return false } check(err1) check(err2) // check sizes if info1.Size() != info2.Size() { return false } // both have the same size, check the contents f1, err := s.DestFs.Open(dst) check(err) defer f1.Close() f2, err := s.SrcFs.Open(src) check(err) defer f2.Close() buf1 := make([]byte, 1000) buf2 := make([]byte, 1000) for { // read from both n1, err := f1.Read(buf1) if err != nil && err != io.EOF { panic(err) } n2, err := f2.Read(buf2) if err != nil && err != io.EOF { panic(err) } // compare read bytes if !bytes.Equal(buf1[:n1], buf2[:n2]) { return false } // end of both files if n1 == 0 && n2 == 0 { break } } return true } // checkDir returns true if dst is a non-empty directory and src is a file func (s *Syncer) checkDir(dst, src string) (b bool, err error) { // read file info dstat, err := s.DestFs.Stat(dst) if os.IsNotExist(err) { return false, nil } else if err != nil { return false, err } sstat, err := s.SrcFs.Stat(src) if err != nil { return false, err } // return false is dst is not a directory or src is a directory if !dstat.IsDir() || sstat.IsDir() { return false, nil } // dst is a directory and src is a file // check if dst is non-empty // read dst directory files, err := afero.ReadDir(s.DestFs, dst) if err != nil { return false, err } if len(files) > 0 { return true, nil } return false, nil } func check(err error) { if err != nil { panic(err) } } fsync-0.9.0/fsync_test.go000066400000000000000000000130331351435352300153560ustar00rootroot00000000000000package fsync import ( "bytes" "io/ioutil" "os" "testing" "time" ) func TestSync(t *testing.T) { // create test directory and chdir to it dir, err := ioutil.TempDir(os.TempDir(), "fsync_test") check(err) check(os.Chdir(dir)) // create test files and directories check(os.MkdirAll("src/a", 0755)) check(ioutil.WriteFile("src/a/b", []byte("file b"), 0644)) check(ioutil.WriteFile("src/c", []byte("file c"), 0644)) // set times in the past to make sure times are synced, not accidentally // the same tt := time.Now().Add(-1 * time.Hour) check(os.Chtimes("src/a/b", tt, tt)) check(os.Chtimes("src/a", tt, tt)) check(os.Chtimes("src/c", tt, tt)) check(os.Chtimes("src", tt, tt)) // create Syncer s := NewSyncer() // sync check(s.SyncTo("dst", "src/a", "src/c")) // check results testDirContents("dst", 2, t) testDirContents("dst/a", 1, t) testFile("dst/a/b", []byte("file b"), t) testFile("dst/c", []byte("file c"), t) testPerms("dst/a", getPerms("src/a"), t) testPerms("dst/a/b", getPerms("src/a/b"), t) testPerms("dst/c", getPerms("src/c"), t) testModTime("dst/a", getModTime("src/a"), t) testModTime("dst/a/b", getModTime("src/a/b"), t) testModTime("dst/c", getModTime("src/c"), t) // sync the parent directory too check(s.Sync("dst", "src")) // check the results testPerms("dst", getPerms("src"), t) testModTime("dst", getModTime("src"), t) // modify src check(ioutil.WriteFile("src/a/b", []byte("file b changed"), 0644)) check(os.Chmod("src/a", 0775)) // sync check(s.Sync("dst", "src")) // check results testFile("dst/a/b", []byte("file b changed"), t) testPerms("dst/a", getPerms("src/a"), t) testModTime("dst", getModTime("src"), t) testModTime("dst/a", getModTime("src/a"), t) testModTime("dst/a/b", getModTime("src/a/b"), t) testModTime("dst/c", getModTime("src/c"), t) // remove c check(os.Remove("src/c")) // sync check(s.Sync("dst", "src")) // check results; c should still exist testDirContents("dst", 2, t) testExistence("dst/c", true, t) // sync s.Delete = true check(s.Sync("dst", "src")) // check results; c should no longer exist testDirContents("dst", 1, t) testExistence("dst/c", false, t) s.Delete = false if err = s.Sync("dst", "src/a/b"); err == nil { t.Errorf("expecting ErrFileOverDir, got nothing.\n") } else if err != nil && err != ErrFileOverDir { panic(err) } } func TestDeleteFileFilter(t *testing.T) { // create test directory and chdir to it dir, err := ioutil.TempDir(os.TempDir(), "fsync_test_delete_filter") check(err) check(os.Chdir(dir)) // create test files and directories check(os.MkdirAll("src/a", 0755)) check(ioutil.WriteFile("src/a/b", []byte("file b"), 0644)) check(os.MkdirAll("dst", 0755)) check(ioutil.WriteFile("dst/c", []byte("file c"), 0644)) check(ioutil.WriteFile("dst/d", []byte("file c"), 0644)) // create Syncer s := NewSyncer() s.Delete = true s.DeleteFilter = func(f os.FileInfo) bool { if f.Name() == "d" { return true } return false } //precondition; dst contains 2 files `c` and `d` testDirContents("dst", 2, t) testExistence("dst/c", true, t) testExistence("dst/d", true, t) check(s.Sync("dst", "src")) // check results; c should no longer exist testDirContents("dst", 2, t) testExistence("dst/a/", true, t) testExistence("dst/a/b", true, t) testExistence("dst/c", false, t) testExistence("dst/d", true, t) } func TestDeleteFileFilterNotSet(t *testing.T) { // create test directory and chdir to it dir, err := ioutil.TempDir(os.TempDir(), "fsync_test_delete_filter") check(err) check(os.Chdir(dir)) // create test files and directories check(os.MkdirAll("src/a", 0755)) check(ioutil.WriteFile("src/a/b", []byte("file b"), 0644)) check(os.MkdirAll("dst", 0755)) check(ioutil.WriteFile("dst/c", []byte("file c"), 0644)) check(ioutil.WriteFile("dst/d", []byte("file c"), 0644)) // create Syncer s := NewSyncer() s.Delete = true //precondition; dst contains 2 files `c` and `d` testDirContents("dst", 2, t) testExistence("dst/c", true, t) testExistence("dst/d", true, t) check(s.Sync("dst", "src")) // check results; c should no longer exist testDirContents("dst", 1, t) testExistence("dst/a/", true, t) testExistence("dst/a/b", true, t) testExistence("dst/c", false, t) testExistence("dst/d", false, t) } func testFile(name string, b []byte, t *testing.T) { testExistence(name, true, t) c, err := ioutil.ReadFile(name) check(err) if !bytes.Equal(b, c) { t.Errorf("content of file \"%s\" is:\n%s\nexpected:\n%s\n", name, c, b) } } func testExistence(name string, e bool, t *testing.T) { _, err := os.Stat(name) if os.IsNotExist(err) { if e { t.Errorf("file \"%s\" does not exist.\n", name) } } else if err != nil { panic(err) } else { if !e { t.Errorf("file \"%s\" exists.\n", name) } } } func testDirContents(name string, count int, t *testing.T) { files, err := ioutil.ReadDir(name) check(err) if len(files) != count { t.Errorf("directory \"%s\" has %d children, shoud have %d.\n", name, len(files), count) } } func testPerms(name string, p os.FileMode, t *testing.T) { p2 := getPerms(name) if p2 != p { t.Errorf("permissions for \"%s\" is %v, should be %v.\n", name, p2, p) } } func testModTime(name string, m time.Time, t *testing.T) { m2 := getModTime(name) if !m2.Equal(m) { t.Errorf("modification time for \"%s\" is %v, should be %v.\n", name, m2, m) } } func getPerms(name string) os.FileMode { info, err := os.Stat(name) check(err) return info.Mode().Perm() } func getModTime(name string) time.Time { info, err := os.Stat(name) check(err) return info.ModTime() } fsync-0.9.0/go.mod000066400000000000000000000000471351435352300137550ustar00rootroot00000000000000module github.com/spf13/fsync go 1.12