package file

import (
 "context"
 "os"
 "time"
)

// Opens the file with shared lock. This function is the same as Open(path, os.O\_RDONLY, RLock)
func OpenToRead(path string) (\*os.File, error) {
 return Open(path, os.O\_RDONLY, RLock)
}

// Tries to lock and opens the file with shared lock. This function is the same as Open(path, os.O\_RDONLY, TryRLock)
func TryOpenToRead(path string) (\*os.File, error) {
 return Open(path, os.O\_RDONLY, TryRLock)
}

// Opens the file with exclusive lock. This function is the same as Open(path, os.O\_RDWR, Lock)
func OpenToUpdate(path string) (\*os.File, error) {
 return Open(path, os.O\_RDWR, Lock)
}

// Tries to lock and opens the file with exclusive lock. This function is the same as Open(path, os.O\_RDWR, TryLock)
func TryOpenToUpdate(path string) (\*os.File, error) {
 return Open(path, os.O\_RDWR, TryLock)
}

// Opens the file with exclusive locking. This function is the same as Open(path, os.O\_CREATE|os.O\_EXCL|os.O\_RDWR, TryLock)
func Create(path string) (\*os.File, error) {
 return Open(path, os.O\_CREATE|os.O\_EXCL|os.O\_RDWR, TryLock)
}

// Opens the file with shared lock. If the file is already locked, tries to lock repeatedly until the conditions is met. This function is the same as OpenContext(ctx, retryDelay, path, RLockContext)
func OpenToReadContext(ctx context.Context, retryDelay time.Duration, path string) (\*os.File, error) {
 return OpenContext(ctx, retryDelay, path, os.O\_RDONLY, RLockContext)
}

// Opens the file with exclusive lock. If the file is already locked, tries to lock repeatedly until the conditions is met. This function is the same as OpenContext(ctx, retryDelay, path, LockContext)
func OpenToUpdateContext(ctx context.Context, retryDelay time.Duration, path string) (\*os.File, error) {
 return OpenContext(ctx, retryDelay, path, os.O\_RDWR, LockContext)
}

// Opens the file with passed locking function.
func Open(path string, flag int, fn func(\*os.File) error) (\*os.File, error) {
 fp, err := openFile(path, flag)
 if err != nil {
 return nil, err
 }

 err = lock(fp, fn)

 if err != nil {
 \_ = fp.Close()
 }
 return fp, err
}

// Opens the file with passed locking function. If failed, try to lock repeatedly until the conditions is met.
func OpenContext(ctx context.Context, retryDelay time.Duration, path string, flag int, fn func(context.Context, time.Duration, \*os.File) error) (\*os.File, error) {
 fp, err := openFile(path, flag)
 if err != nil {
 return nil, err
 }

 err = fn(ctx, retryDelay, fp)

 if err != nil {
 \_ = fp.Close()
 }
 return fp, err
}

func openFile(path string, flag int) (\*os.File, error) {
 var perm os.FileMode = 0600
 if flag == os.O\_RDONLY {
 perm = 0400
 }
 fp, err := os.OpenFile(path, flag, perm)
 if err != nil {
 return nil, NewIOError(err.Error())
 }
 return fp, nil
}

// Places the exclusive lock on the file. If the file is already locked, waits until the file is released.
func Lock(fp \*os.File) error {
 return lock(fp, LockEX)
}

// Places the shared lock on the file. If the file is already locked, waits until the file is released.
func RLock(fp \*os.File) error {
 return lock(fp, LockSH)
}

// Places the exclusive lock on the file. If the file is already locked, returns an error immediately.
func TryLock(fp \*os.File) error {
 return lock(fp, TryLockEX)
}

// Places the shared lock on the file. If the file is already locked, returns an error immediately.
func TryRLock(fp \*os.File) error {
 return lock(fp, TryLockSH)
}

func lock(fp \*os.File, fn func(\*os.File) error) error {
 if err := fn(fp); err != nil {
 return NewLockError(err.Error())
 }
 return nil
}

// Places the exclusive lock on the file. If the file is already locked, tries to lock repeatedly until the conditions is met.
func LockContext(ctx context.Context, retryDelay time.Duration, fp \*os.File) error {
 return lockContext(ctx, retryDelay, fp, TryLock)
}

// Places the shared lock on the file. If the file is already locked, tries to lock repeatedly until the conditions is met.
func RLockContext(ctx context.Context, retryDelay time.Duration, fp \*os.File) error {
 return lockContext(ctx, retryDelay, fp, TryRLock)
}

func lockContext(ctx context.Context, retryDelay time.Duration, fp \*os.File, fn func(\*os.File) error) error {
 if ctx.Err() != nil {
 if ctx.Err() == context.Canceled {
 return NewContextCanceled(ctx.Err().Error())
 }
 return NewContextDone(ctx.Err().Error())
 }

 for {
 if err := fn(fp); err == nil {
 return nil
 }

 select {
 case <-ctx.Done():
 if ctx.Err() == context.Canceled {
 return NewContextCanceled(ctx.Err().Error())
 }
 return NewTimeoutError(fp.Name())
 case <-time.After(retryDelay):
 // try again
 }
 }
}

// Unlocks and closes the file
func Close(fp \*os.File) (err error) {
 defer func() { \_ = fp.Close() }()

 if err = Unlock(fp); err != nil {
 return NewLockError(err.Error())
 }
 return nil
}
ackage 파일import ( "context" "os" "시간")// 공유 잠금으로 파일을 엽니다. 이 함수는 Open(경로, os.O\_RDONLY, RLock)과 동일합니다.함수 OpenToRead(경로 문자열) (\*os.File, error) { return Open(path, os.O\_RDONLY, RLock)}// 공유 잠금으로 파일을 잠그고 열려고 시도합니다. 이 함수는 Open(path, os.O\_RDONLY, TryRLock)과 동일합니다.함수 TryOpenToRead(경로 문자열) (\*os.File, error) { return Open(path, os.O\_RDONLY, TryRLock)}// 전용 잠금으로 파일을 엽니다. 이 함수는 Open(path, os.O\_RDWR, Lock)과 동일합니다.함수 OpenToUpdate(경로 문자열) (\*os.File, error) { return Open(path, os.O\_RDWR, Lock)}// 독점 잠금으로 파일을 잠그고 열려고 시도합니다. 이 함수는 Open(path, os.O\_RDWR, TryLock)과 동일합니다.함수 TryOpenToUpdate(경로 문자열) (\*os.File, error) { return Open(path, os.O\_RDWR, TryLock)}// 독점 잠금으로 파일을 엽니다. 이 함수는 Open(path, os.O\_CREATE|os.O\_EXCL|os.O\_RDWR, TryLock)과 동일합니다.함수 Create(경로 문자열) (\*os.File, error) { return Open(path, os.O\_CREATE|os.O\_EXCL|os.O\_RDWR, TryLock)}// 공유 잠금으로 파일을 엽니다. 파일이 이미 잠긴 경우 조건이 충족될 때까지 반복적으로 잠금을 시도합니다. 이 함수는 OpenContext(ctx, retryDelay, path, RLockContext)와 동일합니다.func OpenToReadContext(ctx context.Context, retryDelay time.Duration, path string) (\*os.File, error) { return OpenContext(ctx, retryDelay, path, os.O\_RDONLY, RLockContext)}// 독점 잠금으로 파일을 엽니다. 파일이 이미 잠긴 경우 조건이 충족될 때까지 반복적으로 잠금을 시도합니다. 이 함수는 OpenContext(ctx, retryDelay, path, LockContext)와 동일합니다.func OpenToUpdateContext(ctx context.Context, retryDelay time.Duration, path string) (\*os.File, error) { return OpenContext(ctx, retryDelay, path, os.O\_RDWR, LockContext)}// 전달된 잠금 함수로 파일을 엽니다.func Open(경로 문자열, 플래그 int, fn func(\*os.File) error) (\*os.File, error) { fp, err := openFile(경로, 플래그) if err != nil { 반환 nil, err } err = lock(fp, fn) if err != nil { \_ = fp.Close() } 반환 fp, err}// 전달된 잠금 함수로 파일을 엽니다. 실패하면 조건이 충족될 때까지 반복적으로 잠금을 시도합니다.func OpenContext(ctx context.Context, retryDelay time.Duration, 경로 문자열, 플래그 int, fn func(context.Context, time.Duration, \*os.File) error) (\*os.File, error) { fp, err := openFile(경로, 플래그) if err != nil { 반환 nil, err } err = fn(ctx, retryDelay, fp) if err != nil { \_ = fp.Close() } 반환 fp, err}함수 openFile(경로 문자열, 플래그 int) (\*os.File, error) { var perm os.FileMode = 0600 if flag == os.O\_RDONLY { perm = 0400 } fp, err := os.OpenFile(경로, 플래그, perm) if err != nil { 반환 nil, NewIOError(err.Error()) } return fp, nil}// 파일에 독점 잠금을 설정합니다. 파일이 이미 잠겨 있으면 파일이 해제될 때까지 기다립니다.함수 Lock(fp \*os.File) error { return lock(fp, LockEX)}// 파일에 공유 잠금을 설정합니다. 파일이 이미 잠긴 경우, 파일이 해제될 때까지 기다립니다.func RLock(fp \*os.File) error { return lock(fp, LockSH)}// 파일에 독점 잠금을 설정합니다. 파일이 이미 잠겨 있으면 즉시 오류를 반환합니다.함수 TryLock(fp \*os.File) error { return lock(fp, TryLockEX)}// 파일에 공유 잠금을 설정합니다. 파일이 이미 잠겨 있으면 즉시 오류를 반환합니다.함수 TryRLock(fp \*os.File) error { return lock(fp, TryLockSH)}func lock(fp \*os.File, fn func(\*os.File) error) error { if err := fn(fp); err != nil { return NewLockError(err.Error()) } return nil}// 파일에 독점 잠금을 설정합니다. 파일이 이미 잠긴 경우 조건이 충족될 때까지 반복적으로 잠금을 시도합니다.func LockContext(ctx context.Context, retryDelay time.Duration, fp \*os.File) error { return lockContext(ctx, retryDelay, fp, TryLock)}// 파일에 공유 잠금을 설정합니다. 파일이 이미 잠긴 경우 조건이 충족될 때까지 반복적으로 잠금을 시도합니다.func RLockContext(ctx context.Context, retryDelay time.Duration, fp \*os.File) error { return lockContext(ctx, retryDelay, fp, TryRLock)}func lockContext(ctx context.Context, retryDelay time.Duration, fp \*os.File, fn func(\*os.File) error) error { ctx.Err() != nil { ctx.Err() == context.Canceled { return NewContextCanceled(ctx.Err().Error()) } return NewContextDone(ctx.Err().Error()) } for { if err := fn(fp); err == nil { return nil } select { case <-ctx.Done(): ctx.Err() == context.Canceled { return NewContextCanceled(ctx.Err().Error()) } return NewTimeoutError(fp.Name()) case <-time.After(retryDelay): // 다시 시도 } }}// 파일 잠금을 해제하고 닫습니다.func Close(fp \*os.File) (err error) { defer func() { \_ = fp.Close() }() if err = Unlock(fp); err != nil { return NewLockError(err.Error()) } return nil}