github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/dirtree/dirtree.go (about) 1 // Package dirtree contains the DirTree type which is used for 2 // building filesystem heirachies in memory. 3 package dirtree 4 5 import ( 6 "bytes" 7 "fmt" 8 "path" 9 "sort" 10 "time" 11 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/lib/errors" 14 ) 15 16 // DirTree is a map of directories to entries 17 type DirTree map[string]fs.DirEntries 18 19 // New returns a fresh DirTree 20 func New() DirTree { 21 return make(DirTree) 22 } 23 24 // parentDir finds the parent directory of path 25 func parentDir(entryPath string) string { 26 dirPath := path.Dir(entryPath) 27 if dirPath == "." { 28 dirPath = "" 29 } 30 return dirPath 31 } 32 33 // Add an entry to the tree 34 // it doesn't create parents 35 func (dt DirTree) Add(entry fs.DirEntry) { 36 dirPath := parentDir(entry.Remote()) 37 dt[dirPath] = append(dt[dirPath], entry) 38 } 39 40 // AddDir adds a directory entry to the tree 41 // this creates the directory itself if required 42 // it doesn't create parents 43 func (dt DirTree) AddDir(entry fs.DirEntry) { 44 dt.Add(entry) 45 // create the directory itself if it doesn't exist already 46 dirPath := entry.Remote() 47 if _, ok := dt[dirPath]; !ok { 48 dt[dirPath] = nil 49 } 50 } 51 52 // AddEntry adds the entry and creates the parents for it regardless 53 // of whether it is a file or a directory. 54 func (dt DirTree) AddEntry(entry fs.DirEntry) { 55 switch entry.(type) { 56 case fs.Directory: 57 dt.AddDir(entry) 58 case fs.Object: 59 dt.Add(entry) 60 default: 61 panic("unknown entry type") 62 } 63 remoteParent := parentDir(entry.Remote()) 64 dt.CheckParent("", remoteParent) 65 } 66 67 // Find returns the DirEntry for filePath or nil if not found 68 func (dt DirTree) Find(filePath string) (parentPath string, entry fs.DirEntry) { 69 parentPath = parentDir(filePath) 70 for _, entry := range dt[parentPath] { 71 if entry.Remote() == filePath { 72 return parentPath, entry 73 } 74 } 75 return parentPath, nil 76 } 77 78 // CheckParent checks that dirPath has a *Dir in its parent 79 func (dt DirTree) CheckParent(root, dirPath string) { 80 if dirPath == root { 81 return 82 } 83 parentPath, entry := dt.Find(dirPath) 84 if entry != nil { 85 return 86 } 87 dt[parentPath] = append(dt[parentPath], fs.NewDir(dirPath, time.Now())) 88 dt.CheckParent(root, parentPath) 89 } 90 91 // CheckParents checks every directory in the tree has *Dir in its parent 92 func (dt DirTree) CheckParents(root string) { 93 for dirPath := range dt { 94 dt.CheckParent(root, dirPath) 95 } 96 } 97 98 // Sort sorts all the Entries 99 func (dt DirTree) Sort() { 100 for _, entries := range dt { 101 sort.Stable(entries) 102 } 103 } 104 105 // Dirs returns the directories in sorted order 106 func (dt DirTree) Dirs() (dirNames []string) { 107 for dirPath := range dt { 108 dirNames = append(dirNames, dirPath) 109 } 110 sort.Strings(dirNames) 111 return dirNames 112 } 113 114 // Prune remove directories from a directory tree. dirNames contains 115 // all directories to remove as keys, with true as values. dirNames 116 // will be modified in the function. 117 func (dt DirTree) Prune(dirNames map[string]bool) error { 118 // We use map[string]bool to avoid recursion (and potential 119 // stack exhaustion). 120 121 // First we need delete directories from their parents. 122 for dName, remove := range dirNames { 123 if !remove { 124 // Currently all values should be 125 // true, therefore this should not 126 // happen. But this makes function 127 // more predictable. 128 fs.Infof(dName, "Directory in the map for prune, but the value is false") 129 continue 130 } 131 if dName == "" { 132 // if dName is root, do nothing (no parent exist) 133 continue 134 } 135 parent := parentDir(dName) 136 // It may happen that dt does not have a dName key, 137 // since directory was excluded based on a filter. In 138 // such case the loop will be skipped. 139 for i, entry := range dt[parent] { 140 switch x := entry.(type) { 141 case fs.Directory: 142 if x.Remote() == dName { 143 // the slice is not sorted yet 144 // to delete item 145 // a) replace it with the last one 146 dt[parent][i] = dt[parent][len(dt[parent])-1] 147 // b) remove last 148 dt[parent] = dt[parent][:len(dt[parent])-1] 149 // we modify a slice within a loop, but we stop 150 // iterating immediately 151 break 152 } 153 case fs.Object: 154 // do nothing 155 default: 156 return errors.Errorf("unknown object type %T", entry) 157 158 } 159 } 160 } 161 162 for len(dirNames) > 0 { 163 // According to golang specs, if new keys were added 164 // during range iteration, they may be skipped. 165 for dName, remove := range dirNames { 166 if !remove { 167 fs.Infof(dName, "Directory in the map for prune, but the value is false") 168 continue 169 } 170 // First, add all subdirectories to dirNames. 171 172 // It may happen that dt[dName] does not exist. 173 // If so, the loop will be skipped. 174 for _, entry := range dt[dName] { 175 switch x := entry.(type) { 176 case fs.Directory: 177 excludeDir := x.Remote() 178 dirNames[excludeDir] = true 179 case fs.Object: 180 // do nothing 181 default: 182 return errors.Errorf("unknown object type %T", entry) 183 184 } 185 } 186 // Then remove current directory from DirTree 187 delete(dt, dName) 188 // and from dirNames 189 delete(dirNames, dName) 190 } 191 } 192 return nil 193 } 194 195 // String emits a simple representation of the DirTree 196 func (dt DirTree) String() string { 197 out := new(bytes.Buffer) 198 for _, dir := range dt.Dirs() { 199 _, _ = fmt.Fprintf(out, "%s/\n", dir) 200 for _, entry := range dt[dir] { 201 flag := "" 202 if _, ok := entry.(fs.Directory); ok { 203 flag = "/" 204 } 205 _, _ = fmt.Fprintf(out, " %s%s\n", path.Base(entry.Remote()), flag) 206 } 207 } 208 return out.String() 209 }