github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/autofile/autofile.go.bak (about)

     1  package autofile
     2  
     3  import (
     4  	"bufio"
     5  	"os"
     6  	"os/signal"
     7  	"sync"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/gnolang/gno/tm2/pkg/random"
    12  )
    13  
    14  /* AutoFile usage
    15  
    16  // Create/Append to ./autofile_test
    17  af, err := OpenAutoFile("autofile_test")
    18  if err != nil {
    19  	panic(err)
    20  }
    21  
    22  // Stream of writes.
    23  // During this time, the file may be moved e.g. by logRotate.
    24  for i := 0; i < 60; i++ {
    25  	af.Write([]byte(Fmt("LOOP(%v)", i)))
    26  	time.Sleep(time.Second)
    27  }
    28  
    29  // Close the AutoFile
    30  err = af.Close()
    31  if err != nil {
    32  	panic(err)
    33  }
    34  */
    35  
    36  const (
    37  	autoFileClosePeriod = 1000 * time.Millisecond
    38  	autoFilePerms       = os.FileMode(0600)
    39  )
    40  
    41  // AutoFile automatically closes and re-opens file for writing. The file is
    42  // automatically setup to close itself every 1s and upon receiving SIGHUP.
    43  //
    44  // This is useful for using a log file with the logrotate tool.
    45  type AutoFile struct {
    46  	ID   string
    47  	Path string
    48  
    49  	closeTicker      *time.Ticker
    50  	closeTickerStopc chan struct{} // closed when closeTicker is stopped
    51  	hupc             chan os.Signal
    52  
    53  	mtx  sync.Mutex
    54  	buf  *bufio.Writer
    55  	file *os.File
    56  }
    57  
    58  // OpenAutoFile creates an AutoFile in the path (with random ID). If there is
    59  // an error, it will be of type *PathError or *ErrPermissionsChanged (if file's
    60  // permissions got changed (should be 0600)).
    61  func OpenAutoFile(path string) (*AutoFile, error) {
    62  	af := &AutoFile{
    63  		ID:               random.RandStr(12) + ":" + path,
    64  		Path:             path,
    65  		closeTicker:      time.NewTicker(autoFileClosePeriod),
    66  		closeTickerStopc: make(chan struct{}),
    67  	}
    68  	if err := af.openFile(); err != nil {
    69  		af.Close()
    70  		return nil, err
    71  	}
    72  
    73  	// Close file on SIGHUP.
    74  	af.hupc = make(chan os.Signal, 1)
    75  	signal.Notify(af.hupc, syscall.SIGHUP)
    76  	go func() {
    77  		for range af.hupc {
    78  			af.closeFile()
    79  		}
    80  	}()
    81  
    82  	go af.closeFileRoutine()
    83  
    84  	return af, nil
    85  }
    86  
    87  // Close shuts down the closing goroutine, SIGHUP handler and closes the
    88  // AutoFile.
    89  func (af *AutoFile) Close() error {
    90  	af.closeTicker.Stop()
    91  	close(af.closeTickerStopc)
    92  	if af.hupc != nil {
    93  		close(af.hupc)
    94  	}
    95  	return af.closeFile()
    96  }
    97  
    98  func (af *AutoFile) closeFileRoutine() {
    99  	for {
   100  		select {
   101  		case <-af.closeTicker.C:
   102  			af.closeFile()
   103  		case <-af.closeTickerStopc:
   104  			return
   105  		}
   106  	}
   107  }
   108  
   109  func (af *AutoFile) closeFile() (err error) {
   110  	af.mtx.Lock()
   111  	defer af.mtx.Unlock()
   112  
   113  	if af.file == nil {
   114  		return nil
   115  	}
   116  	if af.buf == nil {
   117  		panic("should not happen")
   118  	}
   119  
   120  	// sync before closing the file.
   121  	af.sync()
   122  
   123  	// close file and return error.
   124  	err = af.file.Close()
   125  	if err != nil {
   126  		return err
   127  	} else {
   128  		af.buf = nil
   129  		af.file = nil
   130  		return nil
   131  	}
   132  }
   133  
   134  // Write writes len(b) bytes to the AutoFile. It returns the number of bytes
   135  // written and an error, if any. Write returns a non-nil error when n !=
   136  // len(b).
   137  // Opens AutoFile if needed.
   138  func (af *AutoFile) Write(b []byte) (n int, err error) {
   139  	af.mtx.Lock()
   140  	defer af.mtx.Unlock()
   141  
   142  	if af.file == nil {
   143  		if err = af.openFile(); err != nil {
   144  			return
   145  		}
   146  	}
   147  
   148  	n, err = af.buf.Write(b)
   149  	return
   150  }
   151  
   152  // Sync commits the current contents of the file to stable storage. Typically,
   153  // this means flushing the file system's in-memory copy of recently written
   154  // data to disk.
   155  // Opens AutoFile if needed.
   156  func (af *AutoFile) Sync() error {
   157  	af.mtx.Lock()
   158  	defer af.mtx.Unlock()
   159  
   160  	return af.sync()
   161  }
   162  
   163  func (af *AutoFile) sync() error {
   164  	if af.file == nil {
   165  		if err := af.openFile(); err != nil {
   166  			return err
   167  		}
   168  	}
   169  	err := af.buf.Flush()
   170  	if err != nil {
   171  		return err
   172  	}
   173  	return af.file.Sync()
   174  }
   175  
   176  func (af *AutoFile) openFile() error {
   177  	file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, autoFilePerms)
   178  	if err != nil {
   179  		return err
   180  	}
   181  	// fileInfo, err := file.Stat()
   182  	// if err != nil {
   183  	// 	return err
   184  	// }
   185  	// if fileInfo.Mode() != autoFilePerms {
   186  	// 	return errors.NewErrPermissionsChanged(file.Name(), fileInfo.Mode(), autoFilePerms)
   187  	// }
   188  	if af.buf != nil {
   189  		panic("should not happen")
   190  	}
   191  	af.buf = bufio.NewWriterSize(file, 1024*64)
   192  	af.file = file
   193  	return nil
   194  }
   195  
   196  // Size returns the size of the AutoFile. It returns -1 and an error if fails
   197  // get stats or open file.
   198  // Opens AutoFile if needed.
   199  func (af *AutoFile) Size() (int64, error) {
   200  	af.mtx.Lock()
   201  	defer af.mtx.Unlock()
   202  
   203  	if af.file == nil {
   204  		if err := af.openFile(); err != nil {
   205  			return -1, err
   206  		}
   207  	}
   208  
   209  	filestat, err := af.file.Stat()
   210  	if err != nil {
   211  		return -1, err
   212  	}
   213  	filesize := filestat.Size()
   214  	bufsize := int64(af.buf.Buffered())
   215  	return bufsize + filesize, nil
   216  }