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  }