github.com/cobalt77/jfrog-client-go@v0.14.5/artifactory/services/utils/deleteutils.go (about) 1 package utils 2 3 import ( 4 "errors" 5 "regexp" 6 "strings" 7 8 "github.com/cobalt77/jfrog-client-go/utils" 9 "github.com/cobalt77/jfrog-client-go/utils/errorutils" 10 "github.com/cobalt77/jfrog-client-go/utils/io/content" 11 ) 12 13 func WildcardToDirsPath(deletePattern, searchResult string) (string, error) { 14 if !strings.HasSuffix(deletePattern, "/") { 15 return "", errors.New("Delete pattern must end with \"/\"") 16 } 17 18 regexpPattern := "^" + strings.Replace(deletePattern, "*", "([^/]*|.*)", -1) 19 r, err := regexp.Compile(regexpPattern) 20 errorutils.CheckError(err) 21 if err != nil { 22 return "", err 23 } 24 25 groups := r.FindStringSubmatch(searchResult) 26 if len(groups) > 0 { 27 return groups[0], nil 28 } 29 return "", nil 30 } 31 32 // Write all the dirs to be deleted into 'resultWriter'. 33 // However, skip dirs with files(s) that should not be deleted. 34 // In order to accomplish this, we check if the dirs are a prefix of any artifact, witch means the folder contains the artifact and should not be deleted. 35 // Optimization: In order not to scan for each dir the entire artifact reader and see if it is a prefix or not, we rely on the fact that the dirs and artifacts are sorted. 36 // We have two sorted readers in ascending order, we will start scanning from the beginning of the lists and compare whether the folder is a prefix of the current artifact, 37 // in case this is true the dir should not be deleted and we can move on to the next dir, otherwise we have to continue to the next dir or artifact. 38 // To know this, we will choose to move on with the lexicographic largest between the two. 39 // 40 // candidateDirsReaders - Sorted list of dirs to be deleted. 41 // filesNotToBeDeleteReader - Sorted files that should not be deleted. 42 // resultWriter - The filtered list of dirs to be deleted. 43 func WriteCandidateDirsToBeDeleted(candidateDirsReaders []*content.ContentReader, filesNotToBeDeleteReader *content.ContentReader, resultWriter *content.ContentWriter) (err error) { 44 dirsToBeDeletedReader, err := MergeSortedFiles(candidateDirsReaders, true) 45 if err != nil { 46 return 47 } 48 defer dirsToBeDeletedReader.Close() 49 var candidateDirToBeDeletedPath string 50 var itemNotToBeDeletedLocation string 51 var candidateDirToBeDeleted, artifactNotToBeDeleted *ResultItem 52 for { 53 // Fetch the next 'candidateDirToBeDeleted'. 54 if candidateDirToBeDeleted == nil { 55 candidateDirToBeDeleted = new(ResultItem) 56 if err = dirsToBeDeletedReader.NextRecord(candidateDirToBeDeleted); err != nil { 57 break 58 } 59 if candidateDirToBeDeleted.Name == "." { 60 continue 61 } 62 candidateDirToBeDeletedPath = strings.ToLower(candidateDirToBeDeleted.GetItemRelativePath()) 63 } 64 // Fetch the next 'artifactNotToBeDelete'. 65 if artifactNotToBeDeleted == nil { 66 artifactNotToBeDeleted = new(ResultItem) 67 if err = filesNotToBeDeleteReader.NextRecord(artifactNotToBeDeleted); err != nil { 68 // No artifacts left, write remaining dirs to be deleted to result file. 69 resultWriter.Write(*candidateDirToBeDeleted) 70 writeRemainCandidate(resultWriter, dirsToBeDeletedReader) 71 break 72 } 73 itemNotToBeDeletedLocation = strings.ToLower(artifactNotToBeDeleted.GetItemRelativeLocation()) 74 } 75 // Found an 'artifact not to be deleted' in 'dir to be deleted', therefore skip writing the dir to the result file. 76 if strings.HasPrefix(itemNotToBeDeletedLocation, candidateDirToBeDeletedPath) { 77 candidateDirToBeDeleted = nil 78 continue 79 } 80 // 'artifactNotToBeDeletePath' & 'candidateDirToBeDeletedPath' are both sorted. As a result 'candidateDirToBeDeleted' cant be a prefix for any of the remaining artifacts. 81 if itemNotToBeDeletedLocation > candidateDirToBeDeletedPath { 82 resultWriter.Write(*candidateDirToBeDeleted) 83 candidateDirToBeDeleted = nil 84 continue 85 } 86 artifactNotToBeDeleted = nil 87 } 88 err = filesNotToBeDeleteReader.GetError() 89 filesNotToBeDeleteReader.Reset() 90 return 91 } 92 93 func writeRemainCandidate(cw *content.ContentWriter, mergeResult *content.ContentReader) { 94 for toBeDeleted := new(ResultItem); mergeResult.NextRecord(toBeDeleted) == nil; toBeDeleted = new(ResultItem) { 95 cw.Write(*toBeDeleted) 96 } 97 } 98 99 func FilterCandidateToBeDeleted(deleteCandidates *content.ContentReader, resultWriter *content.ContentWriter) ([]*content.ContentReader, error) { 100 paths := make(map[string]ResultItem) 101 pathsKeys := make([]string, 0, utils.MaxBufferSize) 102 dirsToBeDeleted := []*content.ContentReader{} 103 for candidate := new(ResultItem); deleteCandidates.NextRecord(candidate) == nil; candidate = new(ResultItem) { 104 // Save all dirs candidate in a diffrent temp file. 105 if candidate.Type == "folder" { 106 if candidate.Name == "." { 107 continue 108 } 109 pathsKeys = append(pathsKeys, candidate.GetItemRelativePath()) 110 paths[candidate.GetItemRelativePath()] = *candidate 111 if len(pathsKeys) == utils.MaxBufferSize { 112 sortedCandidateDirsFile, err := SortAndSaveBufferToFile(paths, pathsKeys, true) 113 if err != nil { 114 return nil, err 115 } 116 dirsToBeDeleted = append(dirsToBeDeleted, sortedCandidateDirsFile) 117 // Init buffer. 118 paths = make(map[string]ResultItem) 119 pathsKeys = make([]string, 0, utils.MaxBufferSize) 120 } 121 } else { 122 // Write none dir results. 123 resultWriter.Write(*candidate) 124 } 125 } 126 if err := deleteCandidates.GetError(); err != nil { 127 return nil, err 128 } 129 deleteCandidates.Reset() 130 if len(pathsKeys) > 0 { 131 sortedFile, err := SortAndSaveBufferToFile(paths, pathsKeys, true) 132 if err != nil { 133 return nil, err 134 } 135 dirsToBeDeleted = append(dirsToBeDeleted, sortedFile) 136 } 137 return dirsToBeDeleted, nil 138 }