Skip to main content

Storage

Struct Storage 

Source
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

Source

pub fn new<P: AsRef<Path>>(file_path: P) -> Self

Creates a new Storage instance

Source

pub fn path(&self) -> &Path

Returns the path to the storage file

Source

pub fn lock_file_path(&self) -> &Path

Returns the path to the lock file

Source

pub fn is_sqlite(&self) -> bool

Returns true if the storage file is a SQLite database (based on extension)

Source

pub fn read_lock_info(&self) -> Result<LockFileInfo>

Read the current lock file info (session tracking)

Source

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

Source

pub fn register_session(&self, session: SessionInfo) -> Result<LockFileInfo>

Register a new session in the lock file

Source

pub fn update_heartbeat( &self, session_id: &str, editing: Option<EditLock>, ) -> Result<LockFileInfo>

Update heartbeat for a session

Source

pub fn unregister_session(&self, session_id: &str) -> Result<()>

Unregister a session from the lock file

Source

pub fn get_active_sessions(&self) -> Result<LockFileInfo>

Get current active sessions (for displaying warnings)

Source

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)

Source

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)

Source

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

Source

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

Source

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:

  1. Reloads the file from disk
  2. Checks if the requirement was modified externally (based on modified_at timestamp)
  3. If no external changes, applies the update
  4. If external changes exist, performs field-level conflict detection
  5. Auto-merges non-conflicting changes, returns conflict info for conflicts
§Arguments
  • local_store - The local copy of the store with pending changes
  • original_timestamps - Map of requirement IDs to their modified_at timestamps when loaded
  • modified_requirement_ids - Set of requirement IDs that were modified locally
§Returns
  • SaveResult::Success - No conflicts, save completed
  • SaveResult::Merged - External changes merged, save completed
  • SaveResult::Conflict - Conflict detected, user action required
Source

pub fn save_with_resolution( &self, local_store: &RequirementsStore, requirement_id: Uuid, resolution: ConflictResolution, ) -> Result<RequirementsStore>

Force save with a specific conflict resolution strategy

Source

pub fn get_requirement_timestamps( store: &RequirementsStore, ) -> HashMap<Uuid, DateTime<Utc>>

Get a snapshot of requirement timestamps for conflict detection

Source

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:

  1. Acquires exclusive lock
  2. Reloads fresh data from disk
  3. Merges local non-requirement changes (features, users, config, etc.)
  4. Generates a new SPEC-ID based on fresh state
  5. Adds the new requirement
  6. Saves to disk
  7. 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 changes
  • new_req - The new requirement to add (without SPEC-ID yet)
  • feature_prefix - Optional feature prefix for ID generation
  • type_prefix - Optional type prefix for ID generation
§Returns
  • Ok(AddResult) - Contains updated store, merge count, and assigned SPEC-ID
Source

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

Source

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

Source

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)

Source

pub fn remove_attachment_file( &self, spec_id: &str, stored_path: &str, ) -> Result<()>

Removes an attachment file from disk

Source

pub fn get_attachment_full_path(&self, stored_path: &str) -> PathBuf

Gets the full path to an attachment file

Source

pub fn attachment_exists(&self, stored_path: &str) -> bool

Checks if an attachment file exists

Source

pub fn save_sync_state(&self, state: &GitLabSyncState) -> Result<()>

Save a GitLab sync state record trace:STORY-0325 | ai:claude

Source

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

Source

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

Source

pub fn load_all_sync_states(&self) -> Result<Vec<GitLabSyncState>>

Load all GitLab sync states trace:STORY-0325 | ai:claude

Source

pub fn load_sync_states_by_status( &self, status: SyncStatus, ) -> Result<Vec<GitLabSyncState>>

Load GitLab sync states by status trace:STORY-0325 | ai:claude

Source

pub fn delete_sync_state( &self, requirement_id: Uuid, issue_iid: u64, ) -> Result<bool>

Delete a GitLab sync state trace:STORY-0325 | ai:claude

Source

pub fn queue_list( &self, user_id: &str, include_completed: bool, ) -> Result<Vec<QueueEntry>>

List queue entries for a user

Source

pub fn queue_add(&self, entry: QueueEntry) -> Result<()>

Add an entry to a user’s queue

Source

pub fn queue_remove(&self, user_id: &str, requirement_id: &Uuid) -> Result<()>

Remove an entry from a user’s queue

Source

pub fn queue_reorder(&self, user_id: &str, items: &[(Uuid, i64)]) -> Result<()>

Reorder queue entries

Source

pub fn queue_clear(&self, user_id: &str, completed_only: bool) -> Result<()>

Clear queue entries

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.