From 015f1089ea875a153dd2a18ef42c25145d4868f5 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 11 Jul 2026 22:28:32 +0800
Subject: [PATCH] freeze(mas): snapshot tested 2.2.3 build 20260711.1807

---
 C1.source/privatevoice.src/internal/singleinstance/singleinstance_unix.go |   75 ++++++++++++++++++++++++++++++++++++-
 1 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/C1.source/privatevoice.src/internal/singleinstance/singleinstance_unix.go b/C1.source/privatevoice.src/internal/singleinstance/singleinstance_unix.go
index 4a2e38d..6738053 100755
--- a/C1.source/privatevoice.src/internal/singleinstance/singleinstance_unix.go
+++ b/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

--
Gitblit v1.9.3