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