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