| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  |  * MinIO Cloud Storage, (C) 2016 MinIO, Inc. | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-19 07:23:42 +08:00
										 |  |  | package cmd | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | // TreeWalkResult - Tree walk result carries results of tree walking.
 | 
					
						
							|  |  |  | type TreeWalkResult struct { | 
					
						
							| 
									
										
										
										
											2016-05-26 00:22:39 +08:00
										 |  |  | 	entry string | 
					
						
							|  |  |  | 	end   bool | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | // Return entries that have prefix prefixEntry.
 | 
					
						
							|  |  |  | // Note: input entries are expected to be sorted.
 | 
					
						
							|  |  |  | func filterMatchingPrefix(entries []string, prefixEntry string) []string { | 
					
						
							|  |  |  | 	start := 0 | 
					
						
							|  |  |  | 	end := len(entries) | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		if start == end { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-12-06 15:16:06 +08:00
										 |  |  | 		if HasPrefix(entries[start], prefixEntry) { | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		start++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		if start == end { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-12-06 15:16:06 +08:00
										 |  |  | 		if HasPrefix(entries[end-1], prefixEntry) { | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		end-- | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return entries[start:end] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | // xl.ListDir returns entries with trailing "/" for directories. At the object layer
 | 
					
						
							|  |  |  | // we need to remove this trailing "/" for objects and retain "/" for prefixes before
 | 
					
						
							|  |  |  | // sorting because the trailing "/" can affect the sorting results for certain cases.
 | 
					
						
							|  |  |  | // Ex. lets say entries = ["a-b/", "a/"] and both are objects.
 | 
					
						
							|  |  |  | //     sorting with out trailing "/" = ["a", "a-b"]
 | 
					
						
							|  |  |  | //     sorting with trailing "/"     = ["a-b/", "a/"]
 | 
					
						
							|  |  |  | // Hence if entries[] does not have a case like the above example then isLeaf() check
 | 
					
						
							|  |  |  | // can be delayed till the entry is pushed into the TreeWalkResult channel.
 | 
					
						
							|  |  |  | // delayIsLeafCheck() returns true if isLeaf can be delayed or false if
 | 
					
						
							|  |  |  | // isLeaf should be done in listDir()
 | 
					
						
							|  |  |  | func delayIsLeafCheck(entries []string) bool { | 
					
						
							|  |  |  | 	for i, entry := range entries { | 
					
						
							|  |  |  | 		if i == len(entries)-1 { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// If any byte in the "entry" string is less than '/' then the
 | 
					
						
							|  |  |  | 		// next "entry" should not contain '/' at the same same byte position.
 | 
					
						
							|  |  |  | 		for j := 0; j < len(entry); j++ { | 
					
						
							|  |  |  | 			if entry[j] < '/' { | 
					
						
							|  |  |  | 				if len(entries[i+1]) > j { | 
					
						
							|  |  |  | 					if entries[i+1][j] == '/' { | 
					
						
							|  |  |  | 						return false | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | // ListDirFunc - "listDir" function of type listDirFunc returned by listDirFactory() - explained below.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | type ListDirFunc func(bucket, prefixDir, prefixEntry string) (emptyDir bool, entries []string, delayIsLeaf bool) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsLeafFunc - A function isLeaf of type isLeafFunc is used to detect if an
 | 
					
						
							|  |  |  | // entry is a leaf entry. There are 2 scenarios where isLeaf should behave
 | 
					
						
							|  |  |  | // differently depending on the backend:
 | 
					
						
							|  |  |  | // 1. FS backend object listing - isLeaf is true if the entry
 | 
					
						
							|  |  |  | //    has no trailing "/"
 | 
					
						
							|  |  |  | // 2. Erasure backend object listing - isLeaf is true if the entry
 | 
					
						
							|  |  |  | //    is a directory and contains xl.meta
 | 
					
						
							|  |  |  | type IsLeafFunc func(string, string) bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsLeafDirFunc - A function isLeafDir of type isLeafDirFunc is used to detect
 | 
					
						
							|  |  |  | // if an entry is empty directory.
 | 
					
						
							|  |  |  | type IsLeafDirFunc func(string, string) bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func filterListEntries(bucket, prefixDir string, entries []string, prefixEntry string, isLeaf IsLeafFunc) ([]string, bool) { | 
					
						
							|  |  |  | 	// Listing needs to be sorted.
 | 
					
						
							|  |  |  | 	sort.Strings(entries) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Filter entries that have the prefix prefixEntry.
 | 
					
						
							|  |  |  | 	entries = filterMatchingPrefix(entries, prefixEntry) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Can isLeaf() check be delayed till when it has to be sent down the
 | 
					
						
							|  |  |  | 	// TreeWalkResult channel?
 | 
					
						
							|  |  |  | 	delayIsLeaf := delayIsLeafCheck(entries) | 
					
						
							|  |  |  | 	if delayIsLeaf { | 
					
						
							|  |  |  | 		return entries, true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// isLeaf() check has to happen here so that trailing "/" for objects can be removed.
 | 
					
						
							|  |  |  | 	for i, entry := range entries { | 
					
						
							|  |  |  | 		if isLeaf(bucket, pathJoin(prefixDir, entry)) { | 
					
						
							|  |  |  | 			entries[i] = strings.TrimSuffix(entry, slashSeparator) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Sort again after removing trailing "/" for objects as the previous sort
 | 
					
						
							|  |  |  | 	// does not hold good anymore.
 | 
					
						
							|  |  |  | 	sort.Strings(entries) | 
					
						
							|  |  |  | 	return entries, false | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-05-21 11:48:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | // treeWalk walks directory tree recursively pushing TreeWalkResult into the channel as and when it encounters files.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | func doTreeWalk(ctx context.Context, bucket, prefixDir, entryPrefixMatch, marker string, recursive bool, listDir ListDirFunc, isLeaf IsLeafFunc, isLeafDir IsLeafDirFunc, resultCh chan TreeWalkResult, endWalkCh <-chan struct{}, isEnd bool) (emptyDir bool, treeErr error) { | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	// Example:
 | 
					
						
							|  |  |  | 	// if prefixDir="one/two/three/" and marker="four/five.txt" treeWalk is recursively
 | 
					
						
							|  |  |  | 	// called with prefixDir="one/two/three/four/" and marker="five.txt"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var markerBase, markerDir string | 
					
						
							|  |  |  | 	if marker != "" { | 
					
						
							|  |  |  | 		// Ex: if marker="four/five.txt", markerDir="four/" markerBase="five.txt"
 | 
					
						
							| 
									
										
										
										
											2019-08-07 03:08:58 +08:00
										 |  |  | 		markerSplit := strings.SplitN(marker, SlashSeparator, 2) | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 		markerDir = markerSplit[0] | 
					
						
							|  |  |  | 		if len(markerSplit) == 2 { | 
					
						
							| 
									
										
										
										
											2019-08-07 03:08:58 +08:00
										 |  |  | 			markerDir += SlashSeparator | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 			markerBase = markerSplit[1] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-05-11 07:53:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	emptyDir, entries, delayIsLeaf := listDir(bucket, prefixDir, entryPrefixMatch) | 
					
						
							|  |  |  | 	// When isleaf check is delayed, make sure that it is set correctly here.
 | 
					
						
							|  |  |  | 	if delayIsLeaf && isLeaf == nil || isLeafDir == nil { | 
					
						
							|  |  |  | 		return false, errInvalidArgument | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	// For an empty list return right here.
 | 
					
						
							| 
									
										
										
										
											2020-03-14 08:43:00 +08:00
										 |  |  | 	if emptyDir { | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-05-21 11:48:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	// example:
 | 
					
						
							|  |  |  | 	// If markerDir="four/" Search() returns the index of "four/" in the sorted
 | 
					
						
							|  |  |  | 	// entries list so we skip all the entries till "four/"
 | 
					
						
							| 
									
										
										
										
											2016-05-07 17:08:03 +08:00
										 |  |  | 	idx := sort.Search(len(entries), func(i int) bool { | 
					
						
							| 
									
										
										
										
											2016-05-21 11:48:47 +08:00
										 |  |  | 		return entries[i] >= markerDir | 
					
						
							| 
									
										
										
										
											2016-05-07 17:08:03 +08:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	entries = entries[idx:] | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	// For an empty list after search through the entries, return right here.
 | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 	if len(entries) == 0 { | 
					
						
							| 
									
										
										
										
											2020-03-14 08:43:00 +08:00
										 |  |  | 		return false, nil | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-05-11 07:53:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	for i, entry := range entries { | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		var leaf, leafDir bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Decision to do isLeaf check was pushed from listDir() to here.
 | 
					
						
							|  |  |  | 		if delayIsLeaf { | 
					
						
							|  |  |  | 			leaf = isLeaf(bucket, pathJoin(prefixDir, entry)) | 
					
						
							|  |  |  | 			if leaf { | 
					
						
							|  |  |  | 				entry = strings.TrimSuffix(entry, slashSeparator) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			leaf = !strings.HasSuffix(entry, slashSeparator) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if strings.HasSuffix(entry, slashSeparator) { | 
					
						
							|  |  |  | 			leafDir = isLeafDir(bucket, pathJoin(prefixDir, entry)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		isDir := !leafDir && !leaf | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 		if i == 0 && markerDir == entry { | 
					
						
							|  |  |  | 			if !recursive { | 
					
						
							|  |  |  | 				// Skip as the marker would already be listed in the previous listing.
 | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-05-09 10:08:21 +08:00
										 |  |  | 			if recursive && !isDir { | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 				// We should not skip for recursive listing and if markerDir is a directory
 | 
					
						
							|  |  |  | 				// for ex. if marker is "four/five.txt" markerDir will be "four/" which
 | 
					
						
							| 
									
										
										
										
											2016-05-31 07:51:59 +08:00
										 |  |  | 				// should not be skipped, instead it will need to be treeWalk()'ed into.
 | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// Skip if it is a file though as it would be listed in previous listing.
 | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-05-09 10:08:21 +08:00
										 |  |  | 		if recursive && isDir { | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 			// If the entry is a directory, we will need recurse into it.
 | 
					
						
							|  |  |  | 			markerArg := "" | 
					
						
							|  |  |  | 			if entry == markerDir { | 
					
						
							|  |  |  | 				// We need to pass "five.txt" as marker only if we are
 | 
					
						
							|  |  |  | 				// recursing into "four/"
 | 
					
						
							|  |  |  | 				markerArg = markerBase | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			prefixMatch := "" // Valid only for first level treeWalk and empty for subdirectories.
 | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 			// markIsEnd is passed to this entry's treeWalk() so that treeWalker.end can be marked
 | 
					
						
							|  |  |  | 			// true at the end of the treeWalk stream.
 | 
					
						
							|  |  |  | 			markIsEnd := i == len(entries)-1 && isEnd | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 			emptyDir, err := doTreeWalk(ctx, bucket, pathJoin(prefixDir, entry), prefixMatch, markerArg, recursive, | 
					
						
							|  |  |  | 				listDir, isLeaf, isLeafDir, resultCh, endWalkCh, markIsEnd) | 
					
						
							| 
									
										
										
										
											2019-05-06 22:52:42 +08:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2020-03-14 08:43:00 +08:00
										 |  |  | 				return false, err | 
					
						
							| 
									
										
										
										
											2019-05-06 22:52:42 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// A nil totalFound means this is an empty directory that
 | 
					
						
							|  |  |  | 			// needs to be sent to the result channel, otherwise continue
 | 
					
						
							|  |  |  | 			// to the next entry.
 | 
					
						
							| 
									
										
										
										
											2020-03-14 08:43:00 +08:00
										 |  |  | 			if !emptyDir { | 
					
						
							| 
									
										
										
										
											2019-05-06 22:52:42 +08:00
										 |  |  | 				continue | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-04-24 05:54:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 		// EOF is set if we are at last entry and the caller indicated we at the end.
 | 
					
						
							|  |  |  | 		isEOF := ((i == len(entries)-1) && isEnd) | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 		select { | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 		case <-endWalkCh: | 
					
						
							| 
									
										
										
										
											2020-03-14 08:43:00 +08:00
										 |  |  | 			return false, errWalkAbort | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		case resultCh <- TreeWalkResult{entry: pathJoin(prefixDir, entry), end: isEOF}: | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is listed.
 | 
					
						
							| 
									
										
										
										
											2020-03-14 08:43:00 +08:00
										 |  |  | 	return false, nil | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Initiate a new treeWalk in a goroutine.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | func startTreeWalk(ctx context.Context, bucket, prefix, marker string, recursive bool, listDir ListDirFunc, isLeaf IsLeafFunc, isLeafDir IsLeafDirFunc, endWalkCh <-chan struct{}) chan TreeWalkResult { | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	// Example 1
 | 
					
						
							|  |  |  | 	// If prefix is "one/two/three/" and marker is "one/two/three/four/five.txt"
 | 
					
						
							|  |  |  | 	// treeWalk is called with prefixDir="one/two/three/" and marker="four/five.txt"
 | 
					
						
							|  |  |  | 	// and entryPrefixMatch=""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Example 2
 | 
					
						
							|  |  |  | 	// if prefix is "one/two/th" and marker is "one/two/three/four/five.txt"
 | 
					
						
							|  |  |  | 	// treeWalk is called with prefixDir="one/two/" and marker="three/four/five.txt"
 | 
					
						
							|  |  |  | 	// and entryPrefixMatch="th"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | 	resultCh := make(chan TreeWalkResult, maxObjectList) | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	entryPrefixMatch := prefix | 
					
						
							|  |  |  | 	prefixDir := "" | 
					
						
							| 
									
										
										
										
											2019-08-07 03:08:58 +08:00
										 |  |  | 	lastIndex := strings.LastIndex(prefix, SlashSeparator) | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | 	if lastIndex != -1 { | 
					
						
							|  |  |  | 		entryPrefixMatch = prefix[lastIndex+1:] | 
					
						
							|  |  |  | 		prefixDir = prefix[:lastIndex+1] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	marker = strings.TrimPrefix(marker, prefixDir) | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	go func() { | 
					
						
							|  |  |  | 		isEnd := true // Indication to start walking the tree with end as true.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		doTreeWalk(ctx, bucket, prefixDir, entryPrefixMatch, marker, recursive, listDir, isLeaf, isLeafDir, resultCh, endWalkCh, isEnd) | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 		close(resultCh) | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 	return resultCh | 
					
						
							| 
									
										
										
										
											2016-05-06 03:51:56 +08:00
										 |  |  | } |