pub struct Storage { /* private fields */ }Expand description
Handles saving and loading requirements from disk with file locking for rudimentary multi-user support
Implementations§
Source§impl Storage
impl Storage
Sourcepub fn lock_file_path(&self) -> &Path
pub fn lock_file_path(&self) -> &Path
Returns the path to the lock file
Sourcepub fn is_sqlite(&self) -> bool
pub fn is_sqlite(&self) -> bool
Returns true if the storage file is a SQLite database (based on extension)
Sourcepub fn read_lock_info(&self) -> Result<LockFileInfo>
pub fn read_lock_info(&self) -> Result<LockFileInfo>
Read the current lock file info (session tracking)
Sourcepub fn write_lock_info(&self, info: &LockFileInfo) -> Result<()>
pub fn write_lock_info(&self, info: &LockFileInfo) -> Result<()>
Write lock file info (session tracking) This atomically updates the lock file with current session info
Sourcepub fn register_session(&self, session: SessionInfo) -> Result<LockFileInfo>
pub fn register_session(&self, session: SessionInfo) -> Result<LockFileInfo>
Register a new session in the lock file
Sourcepub fn update_heartbeat(
&self,
session_id: &str,
editing: Option<EditLock>,
) -> Result<LockFileInfo>
pub fn update_heartbeat( &self, session_id: &str, editing: Option<EditLock>, ) -> Result<LockFileInfo>
Update heartbeat for a session
Sourcepub fn unregister_session(&self, session_id: &str) -> Result<()>
pub fn unregister_session(&self, session_id: &str) -> Result<()>
Unregister a session from the lock file
Sourcepub fn get_active_sessions(&self) -> Result<LockFileInfo>
pub fn get_active_sessions(&self) -> Result<LockFileInfo>
Get current active sessions (for displaying warnings)
Sourcepub fn load(&self) -> Result<RequirementsStore>
pub fn load(&self) -> Result<RequirementsStore>
Loads requirements from file with file locking Automatically detects file type from extension (.db/.sqlite for SQLite, otherwise YAML)
Sourcepub fn save(&self, store: &RequirementsStore) -> Result<()>
pub fn save(&self, store: &RequirementsStore) -> Result<()>
Saves requirements to file with file locking Automatically detects file type from extension (.db/.sqlite for SQLite, otherwise YAML)
Sourcepub fn reload_if_changed(
&self,
current_store: &RequirementsStore,
) -> Result<(RequirementsStore, bool)>
pub fn reload_if_changed( &self, current_store: &RequirementsStore, ) -> Result<(RequirementsStore, bool)>
Reload file from disk, detecting external changes Returns (store, changed) where changed indicates if the file was modified externally
Sourcepub fn update_atomically<F>(&self, update_fn: F) -> Result<RequirementsStore>where
F: FnOnce(&mut RequirementsStore),
pub fn update_atomically<F>(&self, update_fn: F) -> Result<RequirementsStore>where
F: FnOnce(&mut RequirementsStore),
Perform an atomic update operation with proper locking This reloads the file, applies changes, and saves atomically
Sourcepub fn save_with_conflict_detection(
&self,
local_store: &RequirementsStore,
original_timestamps: &HashMap<Uuid, DateTime<Utc>>,
modified_requirement_ids: &[Uuid],
) -> Result<SaveResult>
pub fn save_with_conflict_detection( &self, local_store: &RequirementsStore, original_timestamps: &HashMap<Uuid, DateTime<Utc>>, modified_requirement_ids: &[Uuid], ) -> Result<SaveResult>
Save with conflict detection for a specific requirement
This method:
- Reloads the file from disk
- Checks if the requirement was modified externally (based on modified_at timestamp)
- If no external changes, applies the update
- If external changes exist, performs field-level conflict detection
- Auto-merges non-conflicting changes, returns conflict info for conflicts
§Arguments
local_store- The local copy of the store with pending changesoriginal_timestamps- Map of requirement IDs to their modified_at timestamps when loadedmodified_requirement_ids- Set of requirement IDs that were modified locally
§Returns
SaveResult::Success- No conflicts, save completedSaveResult::Merged- External changes merged, save completedSaveResult::Conflict- Conflict detected, user action required
Sourcepub fn save_with_resolution(
&self,
local_store: &RequirementsStore,
requirement_id: Uuid,
resolution: ConflictResolution,
) -> Result<RequirementsStore>
pub fn save_with_resolution( &self, local_store: &RequirementsStore, requirement_id: Uuid, resolution: ConflictResolution, ) -> Result<RequirementsStore>
Force save with a specific conflict resolution strategy
Sourcepub fn get_requirement_timestamps(
store: &RequirementsStore,
) -> HashMap<Uuid, DateTime<Utc>>
pub fn get_requirement_timestamps( store: &RequirementsStore, ) -> HashMap<Uuid, DateTime<Utc>>
Get a snapshot of requirement timestamps for conflict detection
Sourcepub fn add_requirement_atomic(
&self,
local_store: &RequirementsStore,
new_req: Requirement,
feature_prefix: Option<&str>,
type_prefix: Option<&str>,
) -> Result<AddResult>
pub fn add_requirement_atomic( &self, local_store: &RequirementsStore, new_req: Requirement, feature_prefix: Option<&str>, type_prefix: Option<&str>, ) -> Result<AddResult>
Atomically add a new requirement with reload-before-save
This method:
- Acquires exclusive lock
- Reloads fresh data from disk
- Merges local non-requirement changes (features, users, config, etc.)
- Generates a new SPEC-ID based on fresh state
- Adds the new requirement
- Saves to disk
- Returns updated store with all changes
This ensures:
- No duplicate SPEC-IDs even with concurrent modifications
- External requirement additions are preserved
- Local config/feature/user changes are applied
§Arguments
local_store- The local store with pending config/feature changesnew_req- The new requirement to add (without SPEC-ID yet)feature_prefix- Optional feature prefix for ID generationtype_prefix- Optional type prefix for ID generation
§Returns
Ok(AddResult)- Contains updated store, merge count, and assigned SPEC-ID
Sourcepub fn get_attachments_dir(&self, spec_id: &str) -> Result<PathBuf>
pub fn get_attachments_dir(&self, spec_id: &str) -> Result<PathBuf>
Returns the directory for storing attachments for a given spec_id Creates the directory if it doesn’t exist
Sourcepub fn store_attachment_file(
&self,
spec_id: &str,
source_path: &Path,
) -> Result<(String, u64)>
pub fn store_attachment_file( &self, spec_id: &str, source_path: &Path, ) -> Result<(String, u64)>
Copies a file to the attachments directory for a requirement Returns the relative path to the stored file and the file size
Sourcepub fn store_attachment_bytes(
&self,
spec_id: &str,
filename: &str,
data: &[u8],
) -> Result<(String, u64)>
pub fn store_attachment_bytes( &self, spec_id: &str, filename: &str, data: &[u8], ) -> Result<(String, u64)>
Stores attachment data from bytes (for drag-drop where path isn’t available)
Sourcepub fn remove_attachment_file(
&self,
spec_id: &str,
stored_path: &str,
) -> Result<()>
pub fn remove_attachment_file( &self, spec_id: &str, stored_path: &str, ) -> Result<()>
Removes an attachment file from disk
Sourcepub fn get_attachment_full_path(&self, stored_path: &str) -> PathBuf
pub fn get_attachment_full_path(&self, stored_path: &str) -> PathBuf
Gets the full path to an attachment file
Sourcepub fn attachment_exists(&self, stored_path: &str) -> bool
pub fn attachment_exists(&self, stored_path: &str) -> bool
Checks if an attachment file exists
Sourcepub fn save_sync_state(&self, state: &GitLabSyncState) -> Result<()>
pub fn save_sync_state(&self, state: &GitLabSyncState) -> Result<()>
Save a GitLab sync state record trace:STORY-0325 | ai:claude
Sourcepub fn load_sync_state(
&self,
requirement_id: Uuid,
issue_iid: u64,
) -> Result<Option<GitLabSyncState>>
pub fn load_sync_state( &self, requirement_id: Uuid, issue_iid: u64, ) -> Result<Option<GitLabSyncState>>
Load a GitLab sync state by requirement ID and issue IID trace:STORY-0325 | ai:claude
Sourcepub fn load_sync_states_for_requirement(
&self,
requirement_id: Uuid,
) -> Result<Vec<GitLabSyncState>>
pub fn load_sync_states_for_requirement( &self, requirement_id: Uuid, ) -> Result<Vec<GitLabSyncState>>
Load all GitLab sync states for a requirement trace:STORY-0325 | ai:claude
Sourcepub fn load_all_sync_states(&self) -> Result<Vec<GitLabSyncState>>
pub fn load_all_sync_states(&self) -> Result<Vec<GitLabSyncState>>
Load all GitLab sync states trace:STORY-0325 | ai:claude
Sourcepub fn load_sync_states_by_status(
&self,
status: SyncStatus,
) -> Result<Vec<GitLabSyncState>>
pub fn load_sync_states_by_status( &self, status: SyncStatus, ) -> Result<Vec<GitLabSyncState>>
Load GitLab sync states by status trace:STORY-0325 | ai:claude
Sourcepub fn delete_sync_state(
&self,
requirement_id: Uuid,
issue_iid: u64,
) -> Result<bool>
pub fn delete_sync_state( &self, requirement_id: Uuid, issue_iid: u64, ) -> Result<bool>
Delete a GitLab sync state trace:STORY-0325 | ai:claude
Sourcepub fn queue_list(
&self,
user_id: &str,
include_completed: bool,
) -> Result<Vec<QueueEntry>>
pub fn queue_list( &self, user_id: &str, include_completed: bool, ) -> Result<Vec<QueueEntry>>
List queue entries for a user
Sourcepub fn queue_add(&self, entry: QueueEntry) -> Result<()>
pub fn queue_add(&self, entry: QueueEntry) -> Result<()>
Add an entry to a user’s queue
Sourcepub fn queue_remove(&self, user_id: &str, requirement_id: &Uuid) -> Result<()>
pub fn queue_remove(&self, user_id: &str, requirement_id: &Uuid) -> Result<()>
Remove an entry from a user’s queue