github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/sync/pipe.go (about) 1 package sync 2 3 import ( 4 "context" 5 "fmt" 6 "math/bits" 7 "strconv" 8 "strings" 9 "sync" 10 11 "github.com/aalpar/deheap" 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/fs/fserrors" 14 ) 15 16 // compare two items for order by 17 type lessFn func(a, b fs.ObjectPair) bool 18 19 // pipe provides an unbounded channel like experience 20 // 21 // Note unlike channels these aren't strictly ordered. 22 type pipe struct { 23 mu sync.Mutex 24 c chan struct{} 25 queue []fs.ObjectPair 26 closed bool 27 totalSize int64 28 stats func(items int, totalSize int64) 29 less lessFn 30 fraction int 31 } 32 33 func newPipe(orderBy string, stats func(items int, totalSize int64), maxBacklog int) (*pipe, error) { 34 if maxBacklog < 0 { 35 maxBacklog = (1 << (bits.UintSize - 1)) - 1 // largest positive int 36 } 37 less, fraction, err := newLess(orderBy) 38 if err != nil { 39 return nil, fserrors.FatalError(err) 40 } 41 p := &pipe{ 42 c: make(chan struct{}, maxBacklog), 43 stats: stats, 44 less: less, 45 fraction: fraction, 46 } 47 if p.less != nil { 48 deheap.Init(p) 49 } 50 return p, nil 51 } 52 53 // Len satisfy heap.Interface - must be called with lock held 54 func (p *pipe) Len() int { 55 return len(p.queue) 56 } 57 58 // Len satisfy heap.Interface - must be called with lock held 59 func (p *pipe) Less(i, j int) bool { 60 return p.less(p.queue[i], p.queue[j]) 61 } 62 63 // Swap satisfy heap.Interface - must be called with lock held 64 func (p *pipe) Swap(i, j int) { 65 p.queue[i], p.queue[j] = p.queue[j], p.queue[i] 66 } 67 68 // Push satisfy heap.Interface - must be called with lock held 69 func (p *pipe) Push(item interface{}) { 70 p.queue = append(p.queue, item.(fs.ObjectPair)) 71 } 72 73 // Pop satisfy heap.Interface - must be called with lock held 74 func (p *pipe) Pop() interface{} { 75 old := p.queue 76 n := len(old) 77 item := old[n-1] 78 old[n-1] = fs.ObjectPair{} // avoid memory leak 79 p.queue = old[0 : n-1] 80 return item 81 } 82 83 // Put a pair into the pipe 84 // 85 // It returns ok = false if the context was cancelled 86 // 87 // It will panic if you call it after Close() 88 // 89 // Note that pairs where src==dst aren't counted for stats 90 func (p *pipe) Put(ctx context.Context, pair fs.ObjectPair) (ok bool) { 91 if ctx.Err() != nil { 92 return false 93 } 94 p.mu.Lock() 95 if p.less == nil { 96 // no order-by 97 p.queue = append(p.queue, pair) 98 } else { 99 deheap.Push(p, pair) 100 } 101 size := pair.Src.Size() 102 if size > 0 && pair.Src != pair.Dst { 103 p.totalSize += size 104 } 105 p.stats(len(p.queue), p.totalSize) 106 p.mu.Unlock() 107 select { 108 case <-ctx.Done(): 109 return false 110 case p.c <- struct{}{}: 111 } 112 return true 113 } 114 115 // Get a pair from the pipe 116 // 117 // If fraction is > the mixed fraction set in the pipe then it gets it 118 // from the other end of the heap if order-by is in effect 119 // 120 // It returns ok = false if the context was cancelled or Close() has 121 // been called. 122 func (p *pipe) GetMax(ctx context.Context, fraction int) (pair fs.ObjectPair, ok bool) { 123 if ctx.Err() != nil { 124 return 125 } 126 select { 127 case <-ctx.Done(): 128 return 129 case _, ok = <-p.c: 130 if !ok { 131 return 132 } 133 } 134 p.mu.Lock() 135 if p.less == nil { 136 // no order-by 137 pair = p.queue[0] 138 p.queue[0] = fs.ObjectPair{} // avoid memory leak 139 p.queue = p.queue[1:] 140 } else if p.fraction < 0 || fraction < p.fraction { 141 pair = deheap.Pop(p).(fs.ObjectPair) 142 } else { 143 pair = deheap.PopMax(p).(fs.ObjectPair) 144 } 145 size := pair.Src.Size() 146 if size > 0 && pair.Src != pair.Dst { 147 p.totalSize -= size 148 } 149 if p.totalSize < 0 { 150 p.totalSize = 0 151 } 152 p.stats(len(p.queue), p.totalSize) 153 p.mu.Unlock() 154 return pair, true 155 } 156 157 // Get a pair from the pipe 158 // 159 // It returns ok = false if the context was cancelled or Close() has 160 // been called. 161 func (p *pipe) Get(ctx context.Context) (pair fs.ObjectPair, ok bool) { 162 return p.GetMax(ctx, -1) 163 } 164 165 // Stats reads the number of items in the queue and the totalSize 166 func (p *pipe) Stats() (items int, totalSize int64) { 167 p.mu.Lock() 168 items, totalSize = len(p.queue), p.totalSize 169 p.mu.Unlock() 170 return items, totalSize 171 } 172 173 // Close the pipe 174 // 175 // Writes to a closed pipe will panic as will double closing a pipe 176 func (p *pipe) Close() { 177 p.mu.Lock() 178 close(p.c) 179 p.closed = true 180 p.mu.Unlock() 181 } 182 183 // newLess returns a less function for the heap comparison or nil if 184 // one is not required 185 func newLess(orderBy string) (less lessFn, fraction int, err error) { 186 fraction = -1 187 if orderBy == "" { 188 return nil, fraction, nil 189 } 190 parts := strings.Split(strings.ToLower(orderBy), ",") 191 switch parts[0] { 192 case "name": 193 less = func(a, b fs.ObjectPair) bool { 194 return a.Src.Remote() < b.Src.Remote() 195 } 196 case "size": 197 less = func(a, b fs.ObjectPair) bool { 198 return a.Src.Size() < b.Src.Size() 199 } 200 case "modtime": 201 less = func(a, b fs.ObjectPair) bool { 202 ctx := context.Background() 203 return a.Src.ModTime(ctx).Before(b.Src.ModTime(ctx)) 204 } 205 default: 206 return nil, fraction, fmt.Errorf("unknown --order-by comparison %q", parts[0]) 207 } 208 descending := false 209 if len(parts) > 1 { 210 switch parts[1] { 211 case "ascending", "asc": 212 case "descending", "desc": 213 descending = true 214 case "mixed": 215 fraction = 50 216 if len(parts) > 2 { 217 fraction, err = strconv.Atoi(parts[2]) 218 if err != nil { 219 return nil, fraction, fmt.Errorf("bad mixed fraction --order-by %q", parts[2]) 220 } 221 } 222 223 default: 224 return nil, fraction, fmt.Errorf("unknown --order-by sort direction %q", parts[1]) 225 } 226 } 227 if (fraction >= 0 && len(parts) > 3) || (fraction < 0 && len(parts) > 2) { 228 return nil, fraction, fmt.Errorf("bad --order-by string %q", orderBy) 229 } 230 if descending { 231 oldLess := less 232 less = func(a, b fs.ObjectPair) bool { 233 return !oldLess(a, b) 234 } 235 } 236 return less, fraction, nil 237 }