github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/journal.go (about)

     1  // Copyright (c) 2017-2019 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package gitbe
     6  
     7  import (
     8  	"bufio"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  )
    16  
    17  var (
    18  	ErrBusy     = fmt.Errorf("busy")
    19  	ErrNotFound = fmt.Errorf("not found")
    20  	ErrSameFile = fmt.Errorf("source same as destination")
    21  )
    22  
    23  type journalFile struct {
    24  	file    *os.File
    25  	scanner *bufio.Scanner
    26  }
    27  
    28  // Journal is a generic 1:N file journaler. It uses an in memory mutex to
    29  // coordinate all actions on disk. The journaler assumes text files that are
    30  // "\n" delineated.
    31  type Journal struct {
    32  	sync.Mutex
    33  	journals map[string]*journalFile
    34  }
    35  
    36  // NewJournal creates a new Journal context. One can use a single journal
    37  // context for many journals.
    38  func NewJournal() *Journal {
    39  	return &Journal{
    40  		journals: make(map[string]*journalFile),
    41  	}
    42  }
    43  
    44  // Journal writes content to a journal file. Note that content should not be
    45  // bigger than bufio.Scanner can read per line. If the user does not provide
    46  // "\n" at the end of content string, this function appends it.
    47  func (j *Journal) Journal(filename, content string) error {
    48  	j.Lock()
    49  	defer j.Unlock()
    50  
    51  	if _, ok := j.journals[filename]; ok {
    52  		return ErrBusy
    53  	}
    54  
    55  	if !strings.HasSuffix(content, "\n") {
    56  		content += "\n"
    57  	}
    58  
    59  	f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0640)
    60  	if err != nil {
    61  		return err
    62  	}
    63  	defer f.Sync()
    64  	defer f.Close()
    65  
    66  	_, err = f.Write([]byte(content))
    67  	return err
    68  }
    69  
    70  // Open opens a journal file ready for replay.  Once done replaying the journal
    71  // the journal file needs to be closed. Note that if the journal is open writes
    72  // return ErrBusy.
    73  func (j *Journal) Open(filename string) error {
    74  	j.Lock()
    75  	defer j.Unlock()
    76  
    77  	if _, ok := j.journals[filename]; ok {
    78  		return ErrBusy
    79  	}
    80  
    81  	f, err := os.Open(filename)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	j.journals[filename] = &journalFile{
    87  		file:    f,
    88  		scanner: bufio.NewScanner(f),
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  // Close closes the underlying journal file. If the journal file is not found
    95  // the function returns ErrNotFound.
    96  func (j *Journal) Close(filename string) error {
    97  	j.Lock()
    98  	defer j.Unlock()
    99  
   100  	f, ok := j.journals[filename]
   101  	if !ok {
   102  		return ErrNotFound
   103  	}
   104  	delete(j.journals, filename)
   105  	return f.file.Close()
   106  }
   107  
   108  // Replay reads a single line from the journal file and calls the replay
   109  // function that was provided. If the scanner encounters EOF it returns EOF,
   110  // unlike the scanner API.
   111  func (j *Journal) Replay(filename string, replay func(string) error) error {
   112  	j.Lock()
   113  	f, ok := j.journals[filename]
   114  	if !ok {
   115  		j.Unlock()
   116  		return ErrNotFound
   117  	}
   118  	j.Unlock()
   119  
   120  	// We can run unlocked from here
   121  
   122  	if !f.scanner.Scan() {
   123  		if f.scanner.Err() == nil {
   124  			return io.EOF
   125  		}
   126  		return f.scanner.Err()
   127  	}
   128  
   129  	return replay(f.scanner.Text())
   130  }
   131  
   132  // Copy copies a journal file from source to destination. During the copy
   133  // process the source file remains locked. This call has the same error
   134  // semantics as the Open call.
   135  func (j *Journal) Copy(source, destination string) (err error) {
   136  	err = j.Open(source)
   137  	if err != nil {
   138  		return
   139  	}
   140  	defer func() {
   141  		cerr := j.Close(source)
   142  		if cerr != nil {
   143  			err = cerr
   144  		}
   145  	}()
   146  
   147  	err = fileCopy(source, destination)
   148  	return
   149  }
   150  
   151  // fileCopy copies a file from src to dst. It attempts to detect if the source
   152  // and destination filename are the same and will return ErrSameFile if they
   153  // are. This test can be defeated and it is meant as basic bug detector.
   154  func fileCopy(srcName, dstName string) (err error) {
   155  	// Try a little bit to prevent overwriting the same file.
   156  	src := filepath.Clean(srcName)
   157  	dst := filepath.Clean(dstName)
   158  	if src == dst {
   159  		err = ErrSameFile
   160  		return
   161  	}
   162  	var in, out *os.File
   163  	in, err = os.Open(src)
   164  	if err != nil {
   165  		return
   166  	}
   167  	defer in.Close()
   168  	out, err = os.Create(dst)
   169  	if err != nil {
   170  		return
   171  	}
   172  	defer func() {
   173  		cerr := out.Close()
   174  		if cerr == nil {
   175  			err = cerr
   176  		}
   177  	}()
   178  	if _, err = io.Copy(out, in); err != nil {
   179  		return
   180  	}
   181  	err = out.Sync()
   182  	return
   183  }