github.com/mithrandie/csvq@v1.18.1/lib/file/control_file.go (about)

     1  package file
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	"github.com/mithrandie/go-file/v2"
    10  )
    11  
    12  type ControlFileType int
    13  
    14  const (
    15  	RLock ControlFileType = iota
    16  	Lock
    17  	Temporary
    18  )
    19  
    20  var controlFileTypeLit = map[ControlFileType]string{
    21  	RLock:     "read lock",
    22  	Lock:      "lock",
    23  	Temporary: "temporary",
    24  }
    25  
    26  func (t ControlFileType) String() string {
    27  	return controlFileTypeLit[t]
    28  }
    29  
    30  type ControlFile struct {
    31  	path string
    32  	fp   *os.File
    33  }
    34  
    35  func NewControlFile(path string, fp *os.File) *ControlFile {
    36  	return &ControlFile{
    37  		path: path,
    38  		fp:   fp,
    39  	}
    40  }
    41  
    42  func (m *ControlFile) Close() error {
    43  	if m != nil {
    44  		if m.fp != nil {
    45  			if err := file.Close(m.fp); err != nil {
    46  				return err
    47  			}
    48  		}
    49  
    50  		if Exists(m.path) {
    51  			if err := os.Remove(m.path); err != nil {
    52  				return err
    53  			}
    54  		}
    55  	}
    56  	return nil
    57  }
    58  
    59  func (m *ControlFile) CloseWithErrors() []error {
    60  	var errs []error
    61  	if m != nil {
    62  		if m.fp != nil {
    63  			if err := file.Close(m.fp); err != nil {
    64  				errs = append(errs, err)
    65  			}
    66  		}
    67  
    68  		if Exists(m.path) {
    69  			if err := os.Remove(m.path); err != nil {
    70  				errs = append(errs, err)
    71  			}
    72  		}
    73  	}
    74  	return errs
    75  }
    76  
    77  func CreateControlFileContext(ctx context.Context, filePath string, fileType ControlFileType, retryDelay time.Duration) (*ControlFile, error) {
    78  	if ctx.Err() != nil {
    79  		if ctx.Err() == context.Canceled {
    80  			return nil, NewContextCanceled()
    81  		}
    82  		return nil, NewContextDone(ctx.Err().Error())
    83  	}
    84  
    85  	for {
    86  		f, err := tryCreateControlFile(filePath, fileType)
    87  		if err == nil {
    88  			return f, nil
    89  		}
    90  		if _, ok := err.(*LockError); !ok {
    91  			return nil, err
    92  		}
    93  
    94  		select {
    95  		case <-ctx.Done():
    96  			if ctx.Err() == context.Canceled {
    97  				return nil, NewContextCanceled()
    98  			}
    99  			return nil, NewTimeoutError(filePath)
   100  		case <-time.After(retryDelay):
   101  			// try again
   102  		}
   103  	}
   104  }
   105  
   106  func tryCreateControlFile(filePath string, fileType ControlFileType) (*ControlFile, error) {
   107  	if len(filePath) < 1 {
   108  		return nil, NewLockError("filename not specified")
   109  	}
   110  
   111  	switch fileType {
   112  	case Lock:
   113  		return TryCreateLockFile(filePath)
   114  	case Temporary:
   115  		return TryCreateTempFile(filePath)
   116  	default: //RLock
   117  		return TryCreateRLockFile(filePath)
   118  	}
   119  }
   120  
   121  func TryCreateRLockFile(filePath string) (controlFile *ControlFile, err error) {
   122  	if LockExists(filePath) {
   123  		return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", RLock, filePath))
   124  	}
   125  
   126  	lockFilePath := LockFilePath(filePath)
   127  	lfp, err := file.Create(lockFilePath)
   128  	if err != nil {
   129  		return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", RLock, filePath))
   130  	}
   131  	lockFile := NewControlFile(lockFilePath, lfp)
   132  	defer func() {
   133  		err = NewCompositeError(err, lockFile.Close())
   134  	}()
   135  
   136  	rlockFilePath := RLockFilePath(filePath)
   137  	fp, e := file.Create(rlockFilePath)
   138  	if e != nil {
   139  		return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", RLock, filePath))
   140  	}
   141  
   142  	return NewControlFile(rlockFilePath, fp), nil
   143  }
   144  
   145  func TryCreateLockFile(filePath string) (*ControlFile, error) {
   146  	if LockExists(filePath) || RLockExists(filePath) {
   147  		return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", Lock, filePath))
   148  	}
   149  
   150  	lockFilePath := LockFilePath(filePath)
   151  	fp, err := file.Create(lockFilePath)
   152  	if err != nil {
   153  		return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", Lock, filePath))
   154  	}
   155  	lockFile := NewControlFile(lockFilePath, fp)
   156  
   157  	if RLockExists(filePath) {
   158  		err := NewLockError(fmt.Sprintf("failed to create %s file for %q", Lock, filePath))
   159  		err = NewCompositeError(err, lockFile.Close())
   160  		return nil, err
   161  	}
   162  
   163  	return lockFile, nil
   164  }
   165  
   166  func TryCreateTempFile(filePath string) (*ControlFile, error) {
   167  	tempFilePath := TempFilePath(filePath)
   168  	fp, err := file.Create(tempFilePath)
   169  	if err != nil {
   170  		return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", Temporary, filePath))
   171  	}
   172  
   173  	return NewControlFile(tempFilePath, fp), nil
   174  }