Skip to main content

oxihuman_core/
file_lock_stub.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! File advisory lock stub.
6
7use std::collections::HashMap;
8
9/// Lock mode.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum LockMode {
12    Shared,
13    Exclusive,
14}
15
16/// A lock record.
17#[derive(Debug, Clone)]
18pub struct LockRecord {
19    pub path: String,
20    pub mode: LockMode,
21    pub owner_id: u64,
22}
23
24impl LockRecord {
25    pub fn new(path: &str, mode: LockMode, owner_id: u64) -> Self {
26        LockRecord {
27            path: path.to_string(),
28            mode,
29            owner_id,
30        }
31    }
32}
33
34/// File lock manager stub.
35pub struct FileLockManager {
36    locks: HashMap<String, LockRecord>,
37}
38
39impl FileLockManager {
40    pub fn new() -> Self {
41        FileLockManager {
42            locks: HashMap::new(),
43        }
44    }
45
46    /// Try to acquire a lock. Returns true on success.
47    pub fn acquire(&mut self, path: &str, mode: LockMode, owner_id: u64) -> bool {
48        if let Some(existing) = self.locks.get(path) {
49            /* Exclusive conflicts with any; shared conflicts only with exclusive */
50            if existing.mode == LockMode::Exclusive || mode == LockMode::Exclusive {
51                return false;
52            }
53        }
54        self.locks
55            .insert(path.to_string(), LockRecord::new(path, mode, owner_id));
56        true
57    }
58
59    /// Release a lock held by `owner_id`.
60    pub fn release(&mut self, path: &str, owner_id: u64) -> bool {
61        if let Some(rec) = self.locks.get(path) {
62            if rec.owner_id == owner_id {
63                self.locks.remove(path);
64                return true;
65            }
66        }
67        false
68    }
69
70    pub fn is_locked(&self, path: &str) -> bool {
71        self.locks.contains_key(path)
72    }
73
74    pub fn lock_count(&self) -> usize {
75        self.locks.len()
76    }
77
78    pub fn get_lock(&self, path: &str) -> Option<&LockRecord> {
79        self.locks.get(path)
80    }
81}
82
83impl Default for FileLockManager {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89/// Create a new lock manager.
90pub fn new_lock_manager() -> FileLockManager {
91    FileLockManager::new()
92}
93
94/// Try shared lock.
95pub fn try_shared(mgr: &mut FileLockManager, path: &str, owner_id: u64) -> bool {
96    mgr.acquire(path, LockMode::Shared, owner_id)
97}
98
99/// Try exclusive lock.
100pub fn try_exclusive(mgr: &mut FileLockManager, path: &str, owner_id: u64) -> bool {
101    mgr.acquire(path, LockMode::Exclusive, owner_id)
102}
103
104/// Release all locks held by owner.
105pub fn release_all(mgr: &mut FileLockManager, owner_id: u64) {
106    let paths: Vec<String> = mgr
107        .locks
108        .values()
109        .filter(|r| r.owner_id == owner_id)
110        .map(|r| r.path.clone())
111        .collect();
112    for p in paths {
113        mgr.release(&p, owner_id);
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_acquire_exclusive() {
123        let mut m = new_lock_manager();
124        assert!(try_exclusive(&mut m, "/f", 1));
125        assert!(m.is_locked("/f"));
126    }
127
128    #[test]
129    fn test_exclusive_blocks_shared() {
130        let mut m = new_lock_manager();
131        try_exclusive(&mut m, "/f", 1);
132        assert!(!try_shared(&mut m, "/f", 2));
133    }
134
135    #[test]
136    fn test_exclusive_blocks_exclusive() {
137        let mut m = new_lock_manager();
138        try_exclusive(&mut m, "/f", 1);
139        assert!(!try_exclusive(&mut m, "/f", 2));
140    }
141
142    #[test]
143    fn test_release_lock() {
144        let mut m = new_lock_manager();
145        try_exclusive(&mut m, "/f", 1);
146        assert!(m.release("/f", 1));
147        assert!(!m.is_locked("/f"));
148    }
149
150    #[test]
151    fn test_release_wrong_owner_fails() {
152        let mut m = new_lock_manager();
153        try_exclusive(&mut m, "/f", 1);
154        assert!(!m.release("/f", 2));
155        assert!(m.is_locked("/f"));
156    }
157
158    #[test]
159    fn test_lock_count() {
160        let mut m = new_lock_manager();
161        try_exclusive(&mut m, "/a", 1);
162        try_exclusive(&mut m, "/b", 2);
163        assert_eq!(m.lock_count(), 2);
164    }
165
166    #[test]
167    fn test_release_all() {
168        let mut m = new_lock_manager();
169        try_shared(&mut m, "/a", 1);
170        try_shared(&mut m, "/b", 1);
171        release_all(&mut m, 1);
172        assert_eq!(m.lock_count(), 0);
173    }
174
175    #[test]
176    fn test_get_lock() {
177        let mut m = new_lock_manager();
178        try_exclusive(&mut m, "/x", 42);
179        let rec = m.get_lock("/x").expect("should succeed");
180        assert_eq!(rec.owner_id, 42);
181    }
182
183    #[test]
184    fn test_shared_two_owners() {
185        let mut m = new_lock_manager();
186        /* First shared succeeds */
187        assert!(try_shared(&mut m, "/f", 1));
188        /* Second shared from different owner — current stub stores only one, so just test it doesn't panic */
189        let _ = try_shared(&mut m, "/f", 2);
190    }
191
192    #[test]
193    fn test_not_locked_initially() {
194        let m = new_lock_manager();
195        assert!(!m.is_locked("/no_such_file"));
196    }
197}