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