github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/sync/pipe.go (about)

     1  package sync
     2  
     3  import (
     4  	"context"
     5  	"math/bits"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/aalpar/deheap"
    11  	"github.com/pkg/errors"
    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 posititive 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  func (p *pipe) Put(ctx context.Context, pair fs.ObjectPair) (ok bool) {
    89  	if ctx.Err() != nil {
    90  		return false
    91  	}
    92  	p.mu.Lock()
    93  	if p.less == nil {
    94  		// no order-by
    95  		p.queue = append(p.queue, pair)
    96  	} else {
    97  		deheap.Push(p, pair)
    98  	}
    99  	size := pair.Src.Size()
   100  	if size > 0 {
   101  		p.totalSize += size
   102  	}
   103  	p.stats(len(p.queue), p.totalSize)
   104  	p.mu.Unlock()
   105  	select {
   106  	case <-ctx.Done():
   107  		return false
   108  	case p.c <- struct{}{}:
   109  	}
   110  	return true
   111  }
   112  
   113  // Get a pair from the pipe
   114  //
   115  // If fraction is > the mixed fraction set in the pipe then it gets it
   116  // from the other end of the heap if order-by is in effect
   117  //
   118  // It returns ok = false if the context was cancelled or Close() has
   119  // been called.
   120  func (p *pipe) GetMax(ctx context.Context, fraction int) (pair fs.ObjectPair, ok bool) {
   121  	if ctx.Err() != nil {
   122  		return
   123  	}
   124  	select {
   125  	case <-ctx.Done():
   126  		return
   127  	case _, ok = <-p.c:
   128  		if !ok {
   129  			return
   130  		}
   131  	}
   132  	p.mu.Lock()
   133  	if p.less == nil {
   134  		// no order-by
   135  		pair = p.queue[0]
   136  		p.queue[0] = fs.ObjectPair{} // avoid memory leak
   137  		p.queue = p.queue[1:]
   138  	} else if p.fraction < 0 || fraction < p.fraction {
   139  		pair = deheap.Pop(p).(fs.ObjectPair)
   140  	} else {
   141  		pair = deheap.PopMax(p).(fs.ObjectPair)
   142  	}
   143  	size := pair.Src.Size()
   144  	if size > 0 {
   145  		p.totalSize -= size
   146  	}
   147  	if p.totalSize < 0 {
   148  		p.totalSize = 0
   149  	}
   150  	p.stats(len(p.queue), p.totalSize)
   151  	p.mu.Unlock()
   152  	return pair, true
   153  }
   154  
   155  // Get a pair from the pipe
   156  //
   157  // It returns ok = false if the context was cancelled or Close() has
   158  // been called.
   159  func (p *pipe) Get(ctx context.Context) (pair fs.ObjectPair, ok bool) {
   160  	return p.GetMax(ctx, -1)
   161  }
   162  
   163  // Stats reads the number of items in the queue and the totalSize
   164  func (p *pipe) Stats() (items int, totalSize int64) {
   165  	p.mu.Lock()
   166  	items, totalSize = len(p.queue), p.totalSize
   167  	p.mu.Unlock()
   168  	return items, totalSize
   169  }
   170  
   171  // Close the pipe
   172  //
   173  // Writes to a closed pipe will panic as will double closing a pipe
   174  func (p *pipe) Close() {
   175  	p.mu.Lock()
   176  	close(p.c)
   177  	p.closed = true
   178  	p.mu.Unlock()
   179  }
   180  
   181  // newLess returns a less function for the heap comparison or nil if
   182  // one is not required
   183  func newLess(orderBy string) (less lessFn, fraction int, err error) {
   184  	fraction = -1
   185  	if orderBy == "" {
   186  		return nil, fraction, nil
   187  	}
   188  	parts := strings.Split(strings.ToLower(orderBy), ",")
   189  	switch parts[0] {
   190  	case "name":
   191  		less = func(a, b fs.ObjectPair) bool {
   192  			return a.Src.Remote() < b.Src.Remote()
   193  		}
   194  	case "size":
   195  		less = func(a, b fs.ObjectPair) bool {
   196  			return a.Src.Size() < b.Src.Size()
   197  		}
   198  	case "modtime":
   199  		less = func(a, b fs.ObjectPair) bool {
   200  			ctx := context.Background()
   201  			return a.Src.ModTime(ctx).Before(b.Src.ModTime(ctx))
   202  		}
   203  	default:
   204  		return nil, fraction, errors.Errorf("unknown --order-by comparison %q", parts[0])
   205  	}
   206  	descending := false
   207  	if len(parts) > 1 {
   208  		switch parts[1] {
   209  		case "ascending", "asc":
   210  		case "descending", "desc":
   211  			descending = true
   212  		case "mixed":
   213  			fraction = 50
   214  			if len(parts) > 2 {
   215  				fraction, err = strconv.Atoi(parts[2])
   216  				if err != nil {
   217  					return nil, fraction, errors.Errorf("bad mixed fraction --order-by %q", parts[2])
   218  				}
   219  			}
   220  
   221  		default:
   222  			return nil, fraction, errors.Errorf("unknown --order-by sort direction %q", parts[1])
   223  		}
   224  	}
   225  	if (fraction >= 0 && len(parts) > 3) || (fraction < 0 && len(parts) > 2) {
   226  		return nil, fraction, errors.Errorf("bad --order-by string %q", orderBy)
   227  	}
   228  	if descending {
   229  		oldLess := less
   230  		less = func(a, b fs.ObjectPair) bool {
   231  			return !oldLess(a, b)
   232  		}
   233  	}
   234  	return less, fraction, nil
   235  }