github.com/puellanivis/breton@v0.2.16/lib/files/wrapper/writer.go (about)

     1  package wrapper
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io"
     7  	"net/url"
     8  	"os"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  // Writer implements the files.Writer interface, that buffers all writes until a Sync or Close, before committing.
    14  type Writer struct {
    15  	mu sync.Mutex
    16  
    17  	*Info
    18  	b bytes.Buffer
    19  
    20  	flush chan struct{}
    21  	done  chan struct{}
    22  	errch chan error
    23  }
    24  
    25  // WriteFunc is a function that is intended to write the given byte slice to some
    26  // underlying source returning any error that should be returned during the
    27  // Sync or Close call which is committing the file.
    28  type WriteFunc func([]byte) error
    29  
    30  // NewWriter returns a Writer that is setup to call the given WriteFunc with
    31  // the underlying buffer on every Sync, and Close.
    32  func NewWriter(ctx context.Context, uri *url.URL, f WriteFunc) *Writer {
    33  	wr := &Writer{
    34  		Info:  NewInfo(uri, 0, time.Now()),
    35  		flush: make(chan struct{}),
    36  		done:  make(chan struct{}),
    37  		errch: make(chan error),
    38  	}
    39  
    40  	doWrite := func() error {
    41  		wr.mu.Lock()
    42  		defer wr.mu.Unlock()
    43  
    44  		// Update ModTime to now.
    45  		wr.Info.SetModTime(time.Now())
    46  		return f(wr.b.Bytes())
    47  	}
    48  
    49  	go func() {
    50  		defer func() {
    51  			close(wr.errch)
    52  			close(wr.flush)
    53  		}()
    54  
    55  		for {
    56  			select {
    57  			case <-wr.done:
    58  				// For done, we only send a non-nil err,
    59  				// When we close the errch, it will then return nil errors.
    60  				if err := doWrite(); err != nil {
    61  					wr.errch <- err
    62  				}
    63  				return
    64  
    65  			case <-wr.flush:
    66  				// For flush, we send even nil errors,
    67  				// Otherwise, the Sync() routine would block forever waiting on an errch.
    68  				wr.errch <- doWrite()
    69  
    70  			case <-ctx.Done():
    71  				return
    72  			}
    73  		}
    74  	}()
    75  
    76  	return wr
    77  }
    78  
    79  // Write performs a thread-safe Write to the underlying buffer.
    80  func (w *Writer) Write(b []byte) (n int, err error) {
    81  	w.mu.Lock()
    82  	defer w.mu.Unlock()
    83  
    84  	n, err = w.b.Write(b)
    85  
    86  	w.Info.SetSize(w.b.Len())
    87  
    88  	return n, err
    89  }
    90  
    91  func (w *Writer) signalSync() error {
    92  	w.mu.Lock()
    93  	defer w.mu.Unlock()
    94  
    95  	select {
    96  	case <-w.done:
    97  		// cannot flush a closed Writer.
    98  		return io.ErrClosedPipe
    99  	default:
   100  	}
   101  
   102  	w.flush <- struct{}{}
   103  	return nil
   104  }
   105  
   106  // Sync calls the defined WriteFunc for the Writer with the entire underlying buffer.
   107  func (w *Writer) Sync() error {
   108  	if err := w.signalSync(); err != nil {
   109  		return err
   110  	}
   111  
   112  	// We cannot wait here under Lock, because the sync process requires the Lock.
   113  	return <-w.errch
   114  }
   115  
   116  func (w *Writer) markDone() error {
   117  	w.mu.Lock()
   118  	defer w.mu.Unlock()
   119  
   120  	select {
   121  	case <-w.done:
   122  		// already closed
   123  		return os.ErrClosed
   124  	default:
   125  	}
   126  
   127  	close(w.done)
   128  	return nil
   129  }
   130  
   131  // Close performs a marks the Writer as complete, which also causes a Sync.
   132  func (w *Writer) Close() error {
   133  	if err := w.markDone(); err != nil {
   134  		return err
   135  	}
   136  
   137  	var err error
   138  
   139  	// We cannot wait here under Lock, because the sync process requires the Lock.
   140  	for err2 := range w.errch {
   141  		if err == nil {
   142  			err = err2
   143  		}
   144  	}
   145  
   146  	return err
   147  }