Skip to main content

mecab_ko_dict/
hot_reload.rs

1//! # 실시간 사전 업데이트 (Hot Reload) 모듈
2//!
3//! 무중단 사전 교체, 파일 변경 감지, 버전 관리 기능을 제공합니다.
4//!
5//! ## 주요 기능
6//!
7//! - **파일 변경 감지**: notify 크레이트를 통한 자동 감지
8//! - **무중단 교체**: `RwLock`과 Copy-on-Write 전략
9//! - **델타 업데이트**: 변경분만 적용하여 성능 최적화
10//! - **버전 관리**: 사전 버전 추적 및 롤백 지원
11//! - **스레드 안전성**: 동시 읽기/쓰기 처리
12//!
13//! ## 아키텍처
14//!
15//! ```text
16//! ┌─────────────────────────────────────────┐
17//! │ HotReloadDictionary                     │
18//! │  - RwLock<VersionedDictionary>          │
19//! │  - FileWatcher (notify)                 │
20//! └─────────────────────────────────────────┘
21//!          │                    │
22//!          ▼                    ▼
23//! ┌─────────────────┐  ┌────────────────┐
24//! │ System Dict     │  │ User Dict      │
25//! │ (Read-only)     │  │ (Mutable)      │
26//! └─────────────────┘  └────────────────┘
27//! ```
28//!
29//! ## 사용 예제
30//!
31//! ```rust,ignore
32//! use mecab_ko_dict::hot_reload::{HotReloadDictionary, DeltaUpdate, DeltaUpdateBuilder};
33//!
34//! // 핫 리로드 사전 생성
35//! let dict = HotReloadDictionary::new("/path/to/dict").unwrap();
36//!
37//! // 실시간 엔트리 추가
38//! dict.add_entry("딥러닝", "NNG", -1000i16, None).unwrap();
39//!
40//! // 델타 업데이트 적용
41//! let delta = DeltaUpdateBuilder::new()
42//!     .add("머신러닝", "NNG", -1000)
43//!     .add("자연어처리", "NNG", -1000)
44//!     .build();
45//! dict.apply_delta(delta).unwrap();
46//!
47//! // 버전 관리
48//! let version = dict.current_version();
49//! dict.rollback(version - 1).unwrap();
50//! ```
51//!
52//! ## 성능 특성
53//!
54//! - **읽기**: Lock contention 최소화 (`RwLock`)
55//! - **쓰기**: `Copy-on-Write`로 기존 읽기 영향 없음
56//! - **델타 업데이트**: O(변경분) 복잡도
57//! - **메모리**: 버전당 증분 메모리 사용
58
59use std::collections::VecDeque;
60use std::path::{Path, PathBuf};
61use std::sync::{Arc, RwLock};
62use std::time::{Duration, SystemTime};
63
64use crate::error::{DictError, Result};
65use crate::user_dict::{UserDictionary, UserEntry};
66#[cfg(test)]
67use crate::DictEntry;
68use crate::{Entry, SystemDictionary};
69
70/// 사전 버전
71///
72/// 각 업데이트마다 증가하는 단조 증가 버전 번호입니다.
73pub type Version = u64;
74
75/// 최대 버전 히스토리 크기 (기본값)
76const DEFAULT_MAX_VERSION_HISTORY: usize = 10;
77
78/// 최대 델타 큐 크기
79const DEFAULT_MAX_DELTA_QUEUE: usize = 100;
80
81/// 버전이 있는 사전 데이터
82///
83/// Copy-on-Write 전략을 위해 Arc로 래핑됩니다.
84#[derive(Clone)]
85struct VersionedDictionary {
86    /// 버전 번호
87    version: Version,
88    /// 시스템 사전 (읽기 전용)
89    system_dict: Arc<SystemDictionary>,
90    /// 사용자 사전 (변경 가능)
91    user_dict: Arc<UserDictionary>,
92    /// 타임스탬프
93    timestamp: SystemTime,
94}
95
96impl VersionedDictionary {
97    /// 새 버전 생성 (사용자 사전 업데이트)
98    fn new_version(&self, user_dict: UserDictionary) -> Self {
99        Self {
100            version: self.version + 1,
101            system_dict: Arc::clone(&self.system_dict),
102            user_dict: Arc::new(user_dict),
103            timestamp: SystemTime::now(),
104        }
105    }
106
107    /// 시스템 사전 교체
108    fn with_system_dict(&self, system_dict: SystemDictionary) -> Self {
109        Self {
110            version: self.version + 1,
111            system_dict: Arc::new(system_dict),
112            user_dict: Arc::clone(&self.user_dict),
113            timestamp: SystemTime::now(),
114        }
115    }
116}
117
118/// 핫 리로드 가능한 사전
119///
120/// `RwLock`으로 동시 접근을 제어하며, Copy-on-Write 전략으로 무중단 업데이트를 지원합니다.
121pub struct HotReloadDictionary {
122    /// 현재 사전 (버전 포함)
123    current: Arc<RwLock<VersionedDictionary>>,
124    /// 버전 히스토리 (롤백용)
125    history: Arc<RwLock<VecDeque<VersionedDictionary>>>,
126    /// 최대 히스토리 크기
127    max_history: usize,
128    /// 델타 업데이트 큐
129    delta_queue: Arc<RwLock<VecDeque<DeltaUpdate>>>,
130    /// 최대 델타 큐 크기
131    max_delta_queue: usize,
132    /// 사전 디렉토리 경로
133    dicdir: PathBuf,
134}
135
136impl HotReloadDictionary {
137    /// 사전 디렉토리에서 핫 리로드 사전 생성
138    ///
139    /// # Arguments
140    ///
141    /// * `dicdir` - 사전 디렉토리 경로
142    ///
143    /// # Errors
144    ///
145    /// - 사전 파일을 찾을 수 없는 경우
146    /// - 사전 파일 포맷이 잘못된 경우
147    pub fn new<P: AsRef<Path>>(dicdir: P) -> Result<Self> {
148        let dicdir = dicdir.as_ref().to_path_buf();
149        let system_dict = SystemDictionary::load(&dicdir)?;
150
151        let versioned = VersionedDictionary {
152            version: 1,
153            system_dict: Arc::new(system_dict),
154            user_dict: Arc::new(UserDictionary::new()),
155            timestamp: SystemTime::now(),
156        };
157
158        Ok(Self {
159            current: Arc::new(RwLock::new(versioned)),
160            history: Arc::new(RwLock::new(VecDeque::new())),
161            max_history: DEFAULT_MAX_VERSION_HISTORY,
162            delta_queue: Arc::new(RwLock::new(VecDeque::new())),
163            max_delta_queue: DEFAULT_MAX_DELTA_QUEUE,
164            dicdir,
165        })
166    }
167
168    /// 기본 경로에서 핫 리로드 사전 생성
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if the dictionary files cannot be loaded.
173    pub fn new_default() -> Result<Self> {
174        let system_dict = SystemDictionary::load_default()?;
175        let dicdir = system_dict.dicdir().to_path_buf();
176
177        let versioned = VersionedDictionary {
178            version: 1,
179            system_dict: Arc::new(system_dict),
180            user_dict: Arc::new(UserDictionary::new()),
181            timestamp: SystemTime::now(),
182        };
183
184        Ok(Self {
185            current: Arc::new(RwLock::new(versioned)),
186            history: Arc::new(RwLock::new(VecDeque::new())),
187            max_history: DEFAULT_MAX_VERSION_HISTORY,
188            delta_queue: Arc::new(RwLock::new(VecDeque::new())),
189            max_delta_queue: DEFAULT_MAX_DELTA_QUEUE,
190            dicdir,
191        })
192    }
193
194    /// 최대 버전 히스토리 크기 설정
195    #[must_use]
196    pub const fn with_max_history(mut self, max_history: usize) -> Self {
197        self.max_history = max_history;
198        self
199    }
200
201    /// 최대 델타 큐 크기 설정
202    #[must_use]
203    pub const fn with_max_delta_queue(mut self, max_delta_queue: usize) -> Self {
204        self.max_delta_queue = max_delta_queue;
205        self
206    }
207
208    /// 현재 버전 반환
209    #[must_use]
210    pub fn current_version(&self) -> Version {
211        self.current.read().map(|dict| dict.version).unwrap_or(0)
212    }
213
214    /// 사전 디렉토리 경로 반환
215    #[must_use]
216    pub fn dicdir(&self) -> &Path {
217        &self.dicdir
218    }
219
220    /// 엔트리 조회 (시스템 사전 + 사용자 사전)
221    ///
222    /// # Arguments
223    ///
224    /// * `surface` - 검색할 표면형
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if the dictionary lock cannot be acquired.
229    pub fn lookup(&self, surface: &str) -> Result<Vec<Entry>> {
230        let dict = self.current.read().map_err(|_| {
231            DictError::Format("Failed to acquire read lock on dictionary".to_string())
232        })?;
233
234        let mut results = Vec::new();
235
236        // 시스템 사전 검색
237        if let Some(index) = dict.system_dict.trie().exact_match(surface) {
238            if let Ok(entry) = dict.system_dict.get_entry(index) {
239                results.push(entry.to_entry());
240            }
241        }
242
243        // 사용자 사전 검색
244        let user_entries = dict.user_dict.lookup(surface);
245        results.extend(user_entries.iter().map(|e| e.to_entry()));
246        drop(dict);
247
248        Ok(results)
249    }
250
251    /// 실시간 엔트리 추가
252    ///
253    /// # Arguments
254    ///
255    /// * `surface` - 표면형
256    /// * `pos` - 품사
257    /// * `cost` - 비용 (낮을수록 우선)
258    /// * `reading` - 읽기 (선택)
259    ///
260    /// # Errors
261    ///
262    /// Returns an error if the dictionary lock cannot be acquired.
263    pub fn add_entry(
264        &self,
265        surface: impl Into<String>,
266        pos: impl Into<String>,
267        cost: i16,
268        reading: Option<String>,
269    ) -> Result<Version> {
270        let mut dict = self.current.write().map_err(|_| {
271            DictError::Format("Failed to acquire write lock on dictionary".to_string())
272        })?;
273
274        // 현재 사용자 사전 복사 (Copy-on-Write)
275        let mut new_user_dict = (*dict.user_dict).clone();
276        new_user_dict.add_entry(surface, pos, Some(cost), reading);
277
278        // 버전 히스토리 저장
279        self.save_to_history(&dict)?;
280
281        // 새 버전으로 교체
282        *dict = dict.new_version(new_user_dict);
283
284        Ok(dict.version)
285    }
286
287    /// 엔트리 제거
288    ///
289    /// # Arguments
290    ///
291    /// * `surface` - 제거할 표면형
292    ///
293    /// # Returns
294    ///
295    /// 제거된 엔트리 수
296    ///
297    /// # Errors
298    ///
299    /// Returns an error if the dictionary lock cannot be acquired.
300    pub fn remove_entry(&self, surface: &str) -> Result<(Version, usize)> {
301        let mut dict = self.current.write().map_err(|_| {
302            DictError::Format("Failed to acquire write lock on dictionary".to_string())
303        })?;
304
305        // 현재 사용자 사전 복사
306        let new_user_dict = (*dict.user_dict).clone();
307
308        // 엔트리 제거 (표면형이 일치하는 모든 엔트리)
309        let removed_count = new_user_dict
310            .entries()
311            .iter()
312            .filter(|e| e.surface == surface)
313            .count();
314
315        if removed_count == 0 {
316            return Ok((dict.version, 0));
317        }
318
319        // 새 사용자 사전 생성 (필터링)
320        let filtered_entries: Vec<_> = new_user_dict
321            .entries()
322            .iter()
323            .filter(|e| e.surface != surface)
324            .cloned()
325            .collect();
326
327        let mut rebuilt_dict = UserDictionary::new();
328        for entry in filtered_entries {
329            rebuilt_dict.add_entry_with_ids(
330                entry.surface,
331                entry.pos,
332                entry.cost,
333                entry.left_id,
334                entry.right_id,
335                entry.reading,
336            );
337        }
338
339        // 버전 히스토리 저장
340        self.save_to_history(&dict)?;
341
342        // 새 버전으로 교체
343        *dict = dict.new_version(rebuilt_dict);
344
345        Ok((dict.version, removed_count))
346    }
347
348    /// 엔트리 수정
349    ///
350    /// # Arguments
351    ///
352    /// * `surface` - 수정할 표면형
353    /// * `update_fn` - 업데이트 함수
354    ///
355    /// # Errors
356    ///
357    /// Returns an error if the dictionary lock cannot be acquired.
358    pub fn update_entry<F>(&self, surface: &str, update_fn: F) -> Result<Version>
359    where
360        F: Fn(&mut UserEntry),
361    {
362        let mut dict = self.current.write().map_err(|_| {
363            DictError::Format("Failed to acquire write lock on dictionary".to_string())
364        })?;
365
366        // 현재 사용자 사전 복사
367        let new_user_dict = (*dict.user_dict).clone();
368
369        // 엔트리 수정
370        let updated_entries: Vec<_> = new_user_dict
371            .entries()
372            .iter()
373            .map(|e| {
374                let mut updated = e.clone();
375                if updated.surface == surface {
376                    update_fn(&mut updated);
377                }
378                updated
379            })
380            .collect();
381
382        // 새 사용자 사전 생성
383        let mut rebuilt_dict = UserDictionary::new();
384        for entry in updated_entries {
385            rebuilt_dict.add_entry_with_ids(
386                entry.surface,
387                entry.pos,
388                entry.cost,
389                entry.left_id,
390                entry.right_id,
391                entry.reading,
392            );
393        }
394
395        // 버전 히스토리 저장
396        self.save_to_history(&dict)?;
397
398        // 새 버전으로 교체
399        *dict = dict.new_version(rebuilt_dict);
400
401        Ok(dict.version)
402    }
403
404    /// 델타 업데이트 적용
405    ///
406    /// 여러 변경 사항을 하나의 트랜잭션으로 적용합니다.
407    ///
408    /// # Arguments
409    ///
410    /// * `delta` - 델타 업데이트
411    ///
412    /// # Errors
413    ///
414    /// Returns an error if the dictionary lock cannot be acquired.
415    pub fn apply_delta(&self, delta: DeltaUpdate) -> Result<Version> {
416        let mut dict = self.current.write().map_err(|_| {
417            DictError::Format("Failed to acquire write lock on dictionary".to_string())
418        })?;
419
420        // 현재 사용자 사전 복사
421        let mut new_user_dict = (*dict.user_dict).clone();
422
423        // 제거 작업
424        for surface in &delta.removals {
425            let filtered_entries: Vec<_> = new_user_dict
426                .entries()
427                .iter()
428                .filter(|e| e.surface != *surface)
429                .cloned()
430                .collect();
431
432            let mut rebuilt_dict = UserDictionary::new();
433            for entry in filtered_entries {
434                rebuilt_dict.add_entry_with_ids(
435                    entry.surface,
436                    entry.pos,
437                    entry.cost,
438                    entry.left_id,
439                    entry.right_id,
440                    entry.reading,
441                );
442            }
443            new_user_dict = rebuilt_dict;
444        }
445
446        // 추가 작업
447        for addition in &delta.additions {
448            new_user_dict.add_entry(
449                addition.surface.clone(),
450                addition.pos.clone(),
451                Some(addition.cost),
452                addition.reading.clone(),
453            );
454        }
455
456        // 수정 작업
457        for modification in &delta.modifications {
458            let updated_entries: Vec<_> = new_user_dict
459                .entries()
460                .iter()
461                .map(|e| {
462                    if e.surface == modification.surface {
463                        modification.to_user_entry()
464                    } else {
465                        e.clone()
466                    }
467                })
468                .collect();
469
470            let mut rebuilt_dict = UserDictionary::new();
471            for entry in updated_entries {
472                rebuilt_dict.add_entry_with_ids(
473                    entry.surface,
474                    entry.pos,
475                    entry.cost,
476                    entry.left_id,
477                    entry.right_id,
478                    entry.reading,
479                );
480            }
481            new_user_dict = rebuilt_dict;
482        }
483
484        // 버전 히스토리 저장
485        self.save_to_history(&dict)?;
486
487        // 델타 큐에 추가
488        self.enqueue_delta(delta)?;
489
490        // 새 버전으로 교체
491        *dict = dict.new_version(new_user_dict);
492
493        Ok(dict.version)
494    }
495
496    /// 시스템 사전 리로드
497    ///
498    /// 사전 파일이 변경되었을 때 호출됩니다.
499    ///
500    /// # Errors
501    ///
502    /// Returns an error if the dictionary files cannot be reloaded or the lock cannot be acquired.
503    pub fn reload_system_dict(&self) -> Result<Version> {
504        let mut dict = self.current.write().map_err(|_| {
505            DictError::Format("Failed to acquire write lock on dictionary".to_string())
506        })?;
507
508        // 시스템 사전 다시 로드
509        let new_system_dict = SystemDictionary::load(&self.dicdir)?;
510
511        // 버전 히스토리 저장
512        self.save_to_history(&dict)?;
513
514        // 새 버전으로 교체
515        *dict = dict.with_system_dict(new_system_dict);
516
517        Ok(dict.version)
518    }
519
520    /// 특정 버전으로 롤백
521    ///
522    /// # Arguments
523    ///
524    /// * `target_version` - 롤백할 버전
525    ///
526    /// # Errors
527    ///
528    /// Returns an error if the version is not found in history or locks cannot be acquired.
529    pub fn rollback(&self, target_version: Version) -> Result<()> {
530        let target = {
531            let history = self.history.read().map_err(|_| {
532                DictError::Format("Failed to acquire read lock on history".to_string())
533            })?;
534
535            history
536                .iter()
537                .find(|v| v.version == target_version)
538                .ok_or_else(|| {
539                    DictError::Format(format!("Version {target_version} not found in history"))
540                })?
541                .clone()
542        };
543
544        *self.current.write().map_err(|_| {
545            DictError::Format("Failed to acquire write lock on dictionary".to_string())
546        })? = target;
547
548        Ok(())
549    }
550
551    /// 버전 히스토리 조회
552    ///
553    /// # Errors
554    ///
555    /// Returns an error if locks cannot be acquired.
556    pub fn version_history(&self) -> Result<Vec<VersionInfo>> {
557        let history = self
558            .history
559            .read()
560            .map_err(|_| DictError::Format("Failed to acquire read lock on history".to_string()))?;
561
562        let current = self.current.read().map_err(|_| {
563            DictError::Format("Failed to acquire read lock on dictionary".to_string())
564        })?;
565
566        let mut versions = vec![VersionInfo {
567            version: current.version,
568            timestamp: current.timestamp,
569            user_entry_count: current.user_dict.len(),
570        }];
571
572        versions.extend(history.iter().map(|v| VersionInfo {
573            version: v.version,
574            timestamp: v.timestamp,
575            user_entry_count: v.user_dict.len(),
576        }));
577        drop(history);
578        drop(current);
579
580        versions.sort_by_key(|v| std::cmp::Reverse(v.version));
581
582        Ok(versions)
583    }
584
585    /// 버전 히스토리에 저장
586    fn save_to_history(&self, dict: &VersionedDictionary) -> Result<()> {
587        let mut history = self.history.write().map_err(|_| {
588            DictError::Format("Failed to acquire write lock on history".to_string())
589        })?;
590
591        history.push_back(dict.clone());
592
593        // 최대 크기 초과 시 오래된 버전 제거
594        while history.len() > self.max_history {
595            history.pop_front();
596        }
597        drop(history);
598
599        Ok(())
600    }
601
602    /// 델타 큐에 추가
603    fn enqueue_delta(&self, delta: DeltaUpdate) -> Result<()> {
604        let mut queue = self.delta_queue.write().map_err(|_| {
605            DictError::Format("Failed to acquire write lock on delta queue".to_string())
606        })?;
607
608        queue.push_back(delta);
609
610        while queue.len() > self.max_delta_queue {
611            queue.pop_front();
612        }
613        drop(queue);
614
615        Ok(())
616    }
617
618    /// 델타 히스토리 조회
619    ///
620    /// # Errors
621    ///
622    /// Returns an error if the lock cannot be acquired.
623    pub fn delta_history(&self) -> Result<Vec<DeltaUpdate>> {
624        let queue = self.delta_queue.read().map_err(|_| {
625            DictError::Format("Failed to acquire read lock on delta queue".to_string())
626        })?;
627
628        Ok(queue.iter().cloned().collect())
629    }
630
631    /// 사용자 사전 내보내기
632    ///
633    /// # Errors
634    ///
635    /// Returns an error if the lock cannot be acquired.
636    pub fn export_user_dict(&self) -> Result<UserDictionary> {
637        let dict = self.current.read().map_err(|_| {
638            DictError::Format("Failed to acquire read lock on dictionary".to_string())
639        })?;
640
641        let user_dict = (*dict.user_dict).clone();
642        drop(dict);
643        Ok(user_dict)
644    }
645
646    /// 사용자 사전 가져오기
647    ///
648    /// # Errors
649    ///
650    /// Returns an error if the lock cannot be acquired.
651    pub fn import_user_dict(&self, user_dict: UserDictionary) -> Result<Version> {
652        let mut dict = self.current.write().map_err(|_| {
653            DictError::Format("Failed to acquire write lock on dictionary".to_string())
654        })?;
655
656        self.save_to_history(&dict)?;
657        *dict = dict.new_version(user_dict);
658
659        Ok(dict.version)
660    }
661}
662
663/// 델타 업데이트
664///
665/// 여러 변경 사항을 하나의 트랜잭션으로 묶습니다.
666#[derive(Debug, Clone)]
667pub struct DeltaUpdate {
668    /// 추가할 엔트리
669    additions: Vec<EntryChange>,
670    /// 제거할 엔트리 (표면형)
671    removals: Vec<String>,
672    /// 수정할 엔트리
673    modifications: Vec<EntryChange>,
674}
675
676impl Default for DeltaUpdate {
677    fn default() -> Self {
678        Self::new()
679    }
680}
681
682impl DeltaUpdate {
683    /// 새 델타 업데이트 생성
684    #[must_use]
685    pub const fn new() -> Self {
686        Self {
687            additions: Vec::new(),
688            removals: Vec::new(),
689            modifications: Vec::new(),
690        }
691    }
692
693    /// 빌더 패턴 시작
694    #[must_use]
695    pub const fn builder() -> DeltaUpdateBuilder {
696        DeltaUpdateBuilder::new()
697    }
698
699    /// 추가 작업 수
700    #[must_use]
701    pub fn addition_count(&self) -> usize {
702        self.additions.len()
703    }
704
705    /// 제거 작업 수
706    #[must_use]
707    pub fn removal_count(&self) -> usize {
708        self.removals.len()
709    }
710
711    /// 수정 작업 수
712    #[must_use]
713    pub fn modification_count(&self) -> usize {
714        self.modifications.len()
715    }
716
717    /// 총 변경 작업 수
718    #[must_use]
719    pub fn total_changes(&self) -> usize {
720        self.additions.len() + self.removals.len() + self.modifications.len()
721    }
722}
723
724/// 엔트리 변경 정보
725#[derive(Debug, Clone)]
726pub struct EntryChange {
727    /// 표면형
728    pub surface: String,
729    /// 품사
730    pub pos: String,
731    /// 비용
732    pub cost: i16,
733    /// 읽기
734    pub reading: Option<String>,
735    /// 좌문맥 ID
736    pub left_id: u16,
737    /// 우문맥 ID
738    pub right_id: u16,
739}
740
741impl EntryChange {
742    /// `UserEntry`로 변환
743    fn to_user_entry(&self) -> UserEntry {
744        UserEntry::new(
745            self.surface.clone(),
746            self.pos.clone(),
747            self.cost,
748            self.reading.clone(),
749        )
750        .with_context_ids(self.left_id, self.right_id)
751    }
752}
753
754/// 델타 업데이트 빌더
755pub struct DeltaUpdateBuilder {
756    delta: DeltaUpdate,
757}
758
759impl Default for DeltaUpdateBuilder {
760    fn default() -> Self {
761        Self::new()
762    }
763}
764
765impl DeltaUpdateBuilder {
766    /// 새 빌더 생성
767    #[must_use]
768    pub const fn new() -> Self {
769        Self {
770            delta: DeltaUpdate::new(),
771        }
772    }
773
774    /// 엔트리 추가
775    #[must_use]
776    pub fn add(mut self, surface: impl Into<String>, pos: impl Into<String>, cost: i16) -> Self {
777        self.delta.additions.push(EntryChange {
778            surface: surface.into(),
779            pos: pos.into(),
780            cost,
781            reading: None,
782            left_id: 0,
783            right_id: 0,
784        });
785        self
786    }
787
788    /// 엔트리 추가 (읽기 포함)
789    #[must_use]
790    pub fn add_with_reading(
791        mut self,
792        surface: impl Into<String>,
793        pos: impl Into<String>,
794        cost: i16,
795        reading: impl Into<String>,
796    ) -> Self {
797        self.delta.additions.push(EntryChange {
798            surface: surface.into(),
799            pos: pos.into(),
800            cost,
801            reading: Some(reading.into()),
802            left_id: 0,
803            right_id: 0,
804        });
805        self
806    }
807
808    /// 엔트리 제거
809    #[must_use]
810    pub fn remove(mut self, surface: impl Into<String>) -> Self {
811        self.delta.removals.push(surface.into());
812        self
813    }
814
815    /// 엔트리 수정
816    #[must_use]
817    pub fn modify(mut self, surface: impl Into<String>, pos: impl Into<String>, cost: i16) -> Self {
818        self.delta.modifications.push(EntryChange {
819            surface: surface.into(),
820            pos: pos.into(),
821            cost,
822            reading: None,
823            left_id: 0,
824            right_id: 0,
825        });
826        self
827    }
828
829    /// 델타 업데이트 빌드
830    #[must_use]
831    pub fn build(self) -> DeltaUpdate {
832        self.delta
833    }
834}
835
836/// 버전 정보
837#[derive(Debug, Clone)]
838pub struct VersionInfo {
839    /// 버전 번호
840    pub version: Version,
841    /// 타임스탬프
842    pub timestamp: SystemTime,
843    /// 사용자 사전 엔트리 수
844    pub user_entry_count: usize,
845}
846
847impl VersionInfo {
848    /// 버전 생성 시간 (Duration)
849    #[must_use]
850    pub fn age(&self) -> Option<Duration> {
851        SystemTime::now().duration_since(self.timestamp).ok()
852    }
853}
854
855#[cfg(test)]
856#[allow(clippy::expect_used, clippy::unwrap_used, clippy::vec_init_then_push)]
857mod tests {
858    use super::*;
859    use crate::matrix::DenseMatrix;
860    use crate::trie::TrieBuilder;
861
862    fn create_test_system_dict() -> SystemDictionary {
863        let entries = vec![("가", 0u32), ("가다", 1), ("가방", 2)];
864        let trie_bytes = TrieBuilder::build(&entries).expect("should build trie");
865        let trie = crate::trie::TrieBackend::Owned(crate::trie::Trie::from_vec(trie_bytes));
866        let matrix = crate::matrix::ConnectionMatrix::Dense(DenseMatrix::new(10, 10, 100));
867
868        let mut dict_entries = Vec::new();
869        dict_entries.push(DictEntry::new("가", 1, 1, 100, "NNG,*,T,가,*,*,*,*"));
870        dict_entries.push(DictEntry::new("가다", 2, 2, 200, "VV,*,F,가다,*,*,*,*"));
871        dict_entries.push(DictEntry::new("가방", 3, 3, 300, "NNG,*,T,가방,*,*,*,*"));
872
873        SystemDictionary::new_test(PathBuf::from("./test_dic"), trie, matrix, dict_entries)
874    }
875
876    #[test]
877    fn test_hot_reload_dictionary_add_entry() {
878        let system_dict = create_test_system_dict();
879        let dicdir = system_dict.dicdir().to_path_buf();
880
881        let versioned = VersionedDictionary {
882            version: 1,
883            system_dict: Arc::new(system_dict),
884            user_dict: Arc::new(UserDictionary::new()),
885            timestamp: SystemTime::now(),
886        };
887
888        let dict = HotReloadDictionary {
889            current: Arc::new(RwLock::new(versioned)),
890            history: Arc::new(RwLock::new(VecDeque::new())),
891            max_history: 10,
892            delta_queue: Arc::new(RwLock::new(VecDeque::new())),
893            max_delta_queue: 100,
894            dicdir,
895        };
896
897        let v1 = dict.current_version();
898        assert_eq!(v1, 1);
899
900        let v2 = dict
901            .add_entry("딥러닝", "NNG", -1000, None)
902            .expect("should add entry");
903        assert_eq!(v2, 2);
904
905        let entries = dict.lookup("딥러닝").expect("should lookup");
906        assert_eq!(entries.len(), 1);
907        assert_eq!(entries[0].surface, "딥러닝");
908    }
909
910    #[test]
911    fn test_hot_reload_dictionary_remove_entry() {
912        let system_dict = create_test_system_dict();
913        let dicdir = system_dict.dicdir().to_path_buf();
914
915        let mut user_dict = UserDictionary::new();
916        user_dict.add_entry("딥러닝", "NNG", Some(-1000), None);
917
918        let versioned = VersionedDictionary {
919            version: 1,
920            system_dict: Arc::new(system_dict),
921            user_dict: Arc::new(user_dict),
922            timestamp: SystemTime::now(),
923        };
924
925        let dict = HotReloadDictionary {
926            current: Arc::new(RwLock::new(versioned)),
927            history: Arc::new(RwLock::new(VecDeque::new())),
928            max_history: 10,
929            delta_queue: Arc::new(RwLock::new(VecDeque::new())),
930            max_delta_queue: 100,
931            dicdir,
932        };
933
934        let (version, removed) = dict.remove_entry("딥러닝").expect("should remove");
935        assert_eq!(version, 2);
936        assert_eq!(removed, 1);
937
938        let entries = dict.lookup("딥러닝").expect("should lookup");
939        assert!(entries.is_empty());
940    }
941
942    #[test]
943    fn test_delta_update() {
944        let system_dict = create_test_system_dict();
945        let dicdir = system_dict.dicdir().to_path_buf();
946
947        let versioned = VersionedDictionary {
948            version: 1,
949            system_dict: Arc::new(system_dict),
950            user_dict: Arc::new(UserDictionary::new()),
951            timestamp: SystemTime::now(),
952        };
953
954        let dict = HotReloadDictionary {
955            current: Arc::new(RwLock::new(versioned)),
956            history: Arc::new(RwLock::new(VecDeque::new())),
957            max_history: 10,
958            delta_queue: Arc::new(RwLock::new(VecDeque::new())),
959            max_delta_queue: 100,
960            dicdir,
961        };
962
963        let delta = DeltaUpdate::builder()
964            .add("딥러닝", "NNG", -1000)
965            .add("머신러닝", "NNG", -1000)
966            .add("자연어처리", "NNG", -1000)
967            .build();
968
969        assert_eq!(delta.addition_count(), 3);
970
971        let version = dict.apply_delta(delta).expect("should apply delta");
972        assert_eq!(version, 2);
973
974        let entries = dict.lookup("딥러닝").expect("should lookup");
975        assert_eq!(entries.len(), 1);
976
977        let entries = dict.lookup("머신러닝").expect("should lookup");
978        assert_eq!(entries.len(), 1);
979    }
980
981    #[test]
982    fn test_version_rollback() {
983        let system_dict = create_test_system_dict();
984        let dicdir = system_dict.dicdir().to_path_buf();
985
986        let versioned = VersionedDictionary {
987            version: 1,
988            system_dict: Arc::new(system_dict),
989            user_dict: Arc::new(UserDictionary::new()),
990            timestamp: SystemTime::now(),
991        };
992
993        let dict = HotReloadDictionary {
994            current: Arc::new(RwLock::new(versioned)),
995            history: Arc::new(RwLock::new(VecDeque::new())),
996            max_history: 10,
997            delta_queue: Arc::new(RwLock::new(VecDeque::new())),
998            max_delta_queue: 100,
999            dicdir,
1000        };
1001
1002        // 버전 1
1003        let v1 = dict.current_version();
1004
1005        // 버전 2
1006        dict.add_entry("딥러닝", "NNG", -1000, None)
1007            .expect("should add");
1008
1009        // 버전 3
1010        dict.add_entry("머신러닝", "NNG", -1000, None)
1011            .expect("should add");
1012
1013        assert_eq!(dict.current_version(), 3);
1014
1015        // 버전 1로 롤백
1016        dict.rollback(v1).expect("should rollback");
1017        assert_eq!(dict.current_version(), v1);
1018
1019        let entries = dict.lookup("딥러닝").expect("should lookup");
1020        assert!(entries.is_empty());
1021    }
1022
1023    #[test]
1024    fn test_version_history() {
1025        let system_dict = create_test_system_dict();
1026        let dicdir = system_dict.dicdir().to_path_buf();
1027
1028        let versioned = VersionedDictionary {
1029            version: 1,
1030            system_dict: Arc::new(system_dict),
1031            user_dict: Arc::new(UserDictionary::new()),
1032            timestamp: SystemTime::now(),
1033        };
1034
1035        let dict = HotReloadDictionary {
1036            current: Arc::new(RwLock::new(versioned)),
1037            history: Arc::new(RwLock::new(VecDeque::new())),
1038            max_history: 10,
1039            delta_queue: Arc::new(RwLock::new(VecDeque::new())),
1040            max_delta_queue: 100,
1041            dicdir,
1042        };
1043
1044        dict.add_entry("A", "NNG", 0, None).expect("should add");
1045        dict.add_entry("B", "NNG", 0, None).expect("should add");
1046        dict.add_entry("C", "NNG", 0, None).expect("should add");
1047
1048        let history = dict.version_history().expect("should get history");
1049        assert_eq!(history.len(), 4); // current + 3 history
1050        assert_eq!(history[0].version, 4); // 최신 버전이 먼저
1051    }
1052
1053    #[test]
1054    fn test_update_entry() {
1055        let system_dict = create_test_system_dict();
1056        let dicdir = system_dict.dicdir().to_path_buf();
1057
1058        let mut user_dict = UserDictionary::new();
1059        user_dict.add_entry("딥러닝", "NNG", Some(-1000), None);
1060
1061        let versioned = VersionedDictionary {
1062            version: 1,
1063            system_dict: Arc::new(system_dict),
1064            user_dict: Arc::new(user_dict),
1065            timestamp: SystemTime::now(),
1066        };
1067
1068        let dict = HotReloadDictionary {
1069            current: Arc::new(RwLock::new(versioned)),
1070            history: Arc::new(RwLock::new(VecDeque::new())),
1071            max_history: 10,
1072            delta_queue: Arc::new(RwLock::new(VecDeque::new())),
1073            max_delta_queue: 100,
1074            dicdir,
1075        };
1076
1077        dict.update_entry("딥러닝", |entry| {
1078            entry.cost = -2000;
1079            entry.reading = Some("딥러닝".to_string());
1080        })
1081        .expect("should update");
1082
1083        let entries = dict.lookup("딥러닝").expect("should lookup");
1084        assert_eq!(entries.len(), 1);
1085        assert_eq!(entries[0].cost, -2000);
1086    }
1087}