Ariver
2026-07-12 24f551a39eae849d891da26cc91f90d354e3a2db
C1.source/privatevoice.src/internal/singleinstance/singleinstance_unix.go
@@ -3,10 +3,13 @@
package singleinstance
import (
   "errors"
   "fmt"
   "os"
   "path/filepath"
   "syscall"
   "voicesnap/internal/paths"
)
type unixLock struct {
@@ -16,15 +19,83 @@
// Acquire tries to acquire a file lock. Returns error if another instance holds it.
func Acquire() (Lock, error) {
   lockPath := filepath.Join(os.TempDir(), "privatevoice-input.lock")
   if ensureErr := paths.Ensure(); ensureErr != nil {
      return nil, fmt.Errorf("ensure app lock dir: %w", ensureErr)
   }
   appLockPath := paths.File("privatevoice-input.lock")
   return acquireWithFallback(lockPath, appLockPath)
}
func acquireWithFallback(primaryPath, fallbackPath string) (Lock, error) {
   lock, err := acquirePath(primaryPath)
   if err == nil {
      return lock, nil
   }
   var unavailable lockUnavailableError
   if errors.As(err, &unavailable) {
      return nil, err
   }
   lock, fallbackErr := acquirePath(fallbackPath)
   if fallbackErr != nil {
      return nil, fmt.Errorf("acquire temp lock: %w; acquire app lock: %v", err, fallbackErr)
   }
   return lock, nil
}
type lockOpenError struct {
   path string
   err  error
}
func (e lockOpenError) Error() string {
   return fmt.Sprintf("open lock %s: %v", e.path, e.err)
}
func (e lockOpenError) Unwrap() error {
   return e.err
}
type lockAccessError struct {
   path string
   err  error
}
func (e lockAccessError) Error() string {
   return fmt.Sprintf("lock %s: %v", e.path, e.err)
}
func (e lockAccessError) Unwrap() error {
   return e.err
}
type lockUnavailableError struct {
   path string
   err  error
}
func (e lockUnavailableError) Error() string {
   return fmt.Sprintf("another instance is already running: %s", e.path)
}
func (e lockUnavailableError) Unwrap() error {
   return e.err
}
func acquirePath(lockPath string) (Lock, error) {
   f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
   if err != nil {
      return nil, err
      return nil, lockOpenError{path: lockPath, err: err}
   }
   err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
   if err != nil {
      f.Close()
      return nil, fmt.Errorf("another instance is already running")
      if errors.Is(err, syscall.EWOULDBLOCK) || errors.Is(err, syscall.EAGAIN) {
         return nil, lockUnavailableError{path: lockPath, err: err}
      }
      return nil, lockAccessError{path: lockPath, err: err}
   }
   return &unixLock{file: f}, nil