Ariver
2026-07-12 33f00a1136c9f7e501b989d432b709d632905304
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//go:build !windows
 
package singleinstance
 
import (
    "errors"
    "fmt"
    "os"
    "path/filepath"
    "syscall"
 
    "voicesnap/internal/paths"
)
 
type unixLock struct {
    file *os.File
}
 
// 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, lockOpenError{path: lockPath, err: err}
    }
 
    err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
    if err != nil {
        f.Close()
        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
}
 
func (l *unixLock) Release() {
    if l.file != nil {
        syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN)
        l.file.Close()
        l.file = nil
    }
}