func (p *FilePreparator) prepareCopy(src, dst string) bool { var err error if p.Logger != nil { p.Logger(p, src, dst, false, false) } p.NumFiles += 1 srci, srcerr := os.Lstat(src) dsti, dsterr := os.Lstat(dst) // // File in source but not in destination // if os.IsNotExist(dsterr) && srcerr == nil { if srci.IsDir() { if p.DocIgnore && ignore.IsIgnored(src) { if p.Verbose { fmt.Printf("Ignoring %s\n", src) } return true } res := p.HandleAction(*NewCreateDir(src, dst, srci)) if !res { return false } f, err := os.Open(src) if err != nil { return p.HandleError(err) } defer f.Close() names, err := f.Readdirnames(-1) if err != nil { return p.HandleError(err) } for _, name := range names { if !p.prepareCopy(filepath.Join(src, name), filepath.Join(dst, name)) { return false } } return true } else { var srchash []byte if !srci.IsDir() { srchash, err = repo.GetHash(src, srci, p.Dedup != nil) if err != nil { return p.HandleError(err) } } res := p.HandleAction(*NewCopyFile(src, dst, srchash, srci)) p.TotalBytes += uint64(size(srci)) return res } } // // [Bidir] File in destination but not in source: reverse copy // if p.Bidir && os.IsNotExist(srcerr) && dsterr == nil { // FIXME: this could probably be simplified into // return p.prepareCopy(dst, src) if dsti.IsDir() { if p.DocIgnore && ignore.IsIgnored(dst) { if p.Verbose { fmt.Printf("Ignoring %s\n", dst) } return true } res := p.HandleAction(*NewCreateDir(dst, src, dsti)) if !res { return false } f, err := os.Open(dst) if err != nil { return p.HandleError(err) } defer f.Close() names, err := f.Readdirnames(-1) if err != nil { return p.HandleError(err) } for _, name := range names { if !p.prepareCopy(filepath.Join(src, name), filepath.Join(dst, name)) { return false } } return true } else { var dsthash []byte if !dsti.IsDir() { dsthash, err = repo.GetHash(dst, dsti, p.Dedup != nil) if err != nil { return p.HandleError(err) } } res := p.HandleAction(*NewCopyAction(dst, src, dsthash, size(dsti), "", false, dsti.Mode(), 0)) p.TotalBytes += uint64(size(dsti)) return res } } // // [Dedup] File in destination but not in source: register in dedup // if p.Dedup != nil && os.IsNotExist(srcerr) && dsterr == nil { hash, err := repo.GetHash(dst, dsti, p.CheckHash) if err != nil { if !p.HandleError(err) { return false } } else if hash != nil { p.Dedup[string(hash)] = append(p.Dedup[string(hash)], dst) } } // // Handle stat() errors // if srcerr != nil { return p.HandleError(srcerr) } if dsterr != nil { return p.HandleError(dsterr) } // // Both source and destination are directories, merge // if srci.IsDir() && dsti.IsDir() { if p.DocIgnore && (ignore.IsIgnored(src) || ignore.IsIgnored(dst)) { if p.Verbose { fmt.Printf("Ignoring %s (source and destination)\n", src) } return true } var srcnames map[string]bool if p.Bidir { srcnames = map[string]bool{} } f, err := os.Open(src) if err != nil { return p.HandleError(err) } defer f.Close() names, err := f.Readdirnames(-1) if err != nil { return p.HandleError(err) } for _, name := range names { if p.Bidir { srcnames[name] = true } if !p.prepareCopy(filepath.Join(src, name), filepath.Join(dst, name)) { return false } } if p.Bidir { f, err := os.Open(dst) if err != nil { return p.HandleError(err) } defer f.Close() dstnames, err := f.Readdirnames(-1) if err != nil { return p.HandleError(err) } for _, name := range dstnames { if srcnames[name] { continue } if !p.prepareCopy(filepath.Join(src, name), filepath.Join(dst, name)) { return false } } } return true } // // Source and destination are regular files // If hash is different, there is a conflict // var srch, dsth []byte if !srci.IsDir() { srch, err = repo.GetHash(src, srci, false) computed := false if err == nil && srch == nil { if p.Logger != nil { p.Logger(p, src, dst, true, false) } srch, err = repo.HashFile(src, srci) computed = true } if err == nil && computed && p.Commit { _, err = repo.CommitFileHash(src, srci, srch, false) } if err != nil { return p.HandleError(err) } } if !dsti.IsDir() { dsth, err = repo.GetHash(dst, dsti, false) computed := false if err == nil && dsth == nil { if p.Logger != nil { p.Logger(p, src, dst, false, true) } dsth, err = repo.HashFile(dst, dsti) computed = true } if err == nil && computed && p.Commit { _, err = repo.CommitFileHash(dst, dsti, dsth, false) } if err != nil { return p.HandleError(err) } } if bytes.Equal(srch, dsth) && srci.Mode()&os.ModeSymlink == dsti.Mode()&os.ModeSymlink { return true } if repo.ConflictFile(src) == "" { dstname := repo.FindConflictFileName(dst, srch) if dstname != "" { p.TotalBytes += uint64(size(srci)) if !p.HandleAction(*NewCopyAction(src, dstname, srch, size(srci), dst, true, srci.Mode(), dsti.Mode())) { return false } } } if p.Bidir && repo.ConflictFile(dst) == "" { srcname := repo.FindConflictFileName(src, dsth) if srcname != "" { p.TotalBytes += uint64(size(dsti)) if !p.HandleAction(*NewCopyAction(dst, srcname, dsth, size(dsti), src, true, dsti.Mode(), srci.Mode())) { return false } } } return true }
func mainStatus(args []string) int { f := flag.NewFlagSet("status", flag.ExitOnError) opt_no_par2 := f.Bool("n", false, "Do not show files missing PAR2 redundency data") opt_show_only_hash := f.Bool("c", false, "Show only unchanged committed files with their hash") opt_no_docignore := f.Bool("no-docignore", false, "Don't treat .docignore files specially") f.Usage = func() { fmt.Print(usageStatus) f.PrintDefaults() } f.Parse(args) dir := f.Arg(0) if dir == "" { dir = "." } rep := repo.GetRepo(dir) status := 0 err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) status = 1 return err } // Skip directories containing an empty .docignore file if !*opt_no_docignore && ignore.IsIgnored(path) { return filepath.SkipDir } // Skip .dirstore/ at root if filepath.Base(path) == attrs.DirStoreName && filepath.Dir(path) == dir && info.IsDir() { return filepath.SkipDir } else if !info.Mode().IsRegular() { return nil } if *opt_show_only_hash { hash, err := repo.GetHash(path, info, false) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) return nil } if hash != nil { fmt.Printf("%s\t%s\n", base58.Encode(hash), path) } } else { var conflict string = "" if repo.ConflictFile(path) != "" { conflict = " c" } else if len(repo.ConflictFileAlternatives(path)) > 0 { conflict = " C" } hashTime, err := repo.GetHashTime(path) if repo.IsNoData(err) { if info.Mode()&os.FileMode(0200) == 0 { fmt.Printf("?%s (ro)\t%s\n", conflict, path) } else { fmt.Printf("?%s\t%s\n", conflict, path) } return nil } else if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) return nil } var redundency string = "*" if rep != nil { digest, err := repo.GetHash(path, info, true) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) return nil } if par2exists, _ := rep.Par2Exists(digest); par2exists { redundency = "" } } if hashTime != info.ModTime() { fmt.Printf("+%s%s\t%s\n", conflict, redundency, path) } else if conflict != "" || (redundency != "" && !*opt_no_par2) { fmt.Printf("%s%s\t%s\n", conflict, redundency, path) } } return nil }) if err != nil { fmt.Fprintf(os.Stderr, "%v", err) os.Exit(1) } return status }
func mainInfo(args []string) int { f := flag.NewFlagSet("info", flag.ExitOnError) opt_check := f.Bool("c", false, "Run integrity check") f.Usage = func() { fmt.Print(infoUsage) f.PrintDefaults() } f.Parse(args) dir := f.Arg(0) if dir == "" { dir = "." } rep := repo.GetRepo(dir) status := 0 first := true err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) status = 1 return err } // Skip .dirstore/ at root if filepath.Base(path) == attrs.DirStoreName && filepath.Dir(path) == dir && info.IsDir() { return filepath.SkipDir } else if !info.Mode().IsRegular() { return nil } if first { first = false } else { fmt.Println() } fmt.Printf("File: %s\n", path) if conflict := repo.ConflictFile(path); conflict != "" { fmt.Printf("Conflict With: %s\n", conflict) } for _, alt := range repo.ConflictFileAlternatives(path) { fmt.Printf("Conflict Alternatives: %s\n", alt) } var realHash mh.Multihash if *opt_check { realHash, err = repo.HashFile(path, info) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err) return nil } } hashTime, err := repo.GetHashTime(path) if repo.IsNoData(err) { if *opt_check { fmt.Printf("Actual Hash: %s\n", base58.Encode(realHash)) } fmt.Printf("Status: New\n") } else { if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err.Error()) return nil } fmt.Printf("Hash Time: %v\n", hashTime.Format(time.RFC3339Nano)) hash, err := attrs.Get(path, repo.XattrHash) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err) return nil } var par2exists = false if rep != nil { par2exists, _ = rep.Par2Exists(hash) } fmt.Printf("Recorded Hash: %s (reduncency %s)\n", base58.Encode(hash), boolToAvailableStr(par2exists)) if *opt_check { par2exists = false if rep != nil { par2exists, _ = rep.Par2Exists(realHash) } fmt.Printf("Actual Hash: %s (redundency %s)\n", base58.Encode(realHash), boolToAvailableStr(par2exists)) } if hashTime != info.ModTime() { fmt.Printf("Status: Dirty\n") } else { if *opt_check && !bytes.Equal(realHash, hash) { fmt.Printf("Status: Corrupted\n") } else { fmt.Printf("Status: Clean\n") } } } return nil }) if err != nil { fmt.Fprintf(os.Stderr, "%v", err) os.Exit(1) } return status }