diff --git a/crates/signed_state/src/checkouts.rs b/crates/signed_state/src/checkouts.rs index 52d0214..2b01d06 100644 --- a/crates/signed_state/src/checkouts.rs +++ b/crates/signed_state/src/checkouts.rs @@ -10,8 +10,9 @@ use signed_core::{Announcement, RepoAddr}; use crate::backend::{Backend, BackendEvent}; use crate::git_store::repo_mirror_root; +use crate::local_repos::LocalReposStore; use crate::refresh::{RefreshGate, RefreshRequest}; -use crate::repos::{LocalReposStore, RepoListStore}; +use crate::repos::RepoListStore; const REFRESH_DEBOUNCE: Duration = Duration::from_millis(300); diff --git a/crates/signed_state/src/lib.rs b/crates/signed_state/src/lib.rs index 97afe9f..a936dd9 100644 --- a/crates/signed_state/src/lib.rs +++ b/crates/signed_state/src/lib.rs @@ -2,6 +2,7 @@ mod backend; mod checkouts; mod git_store; mod inbox; +mod local_repos; mod profile; mod refresh; mod repo; @@ -15,11 +16,12 @@ use git_store::set_git_cache; pub use git_store::{ensure_repo_mirror, open_repo_mirror, repo_mirror_path}; use gpui::{App, AppContext}; pub use inbox::{Inbox, query_inbox}; +pub use local_repos::LocalReposStore; pub use nostr_sdk::prelude::Timestamp; pub use profile::{Profile, ProfileStore}; pub use refresh::{RefreshGate, RefreshRequest}; pub use repo::RepoStore; -pub use repos::{LocalReposStore, RepoActivityCounts, RepoListStore}; +pub use repos::{RepoActivityCounts, RepoListStore}; use signed_nostr::new_backend; #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/signed_state/src/local_repos.rs b/crates/signed_state/src/local_repos.rs new file mode 100644 index 0000000..95b6a55 --- /dev/null +++ b/crates/signed_state/src/local_repos.rs @@ -0,0 +1,105 @@ +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use anyhow::Error; +use gpui::{App, AppContext, Context, Entity, Global, Task}; +use signed_git::find_git_repos; + +struct GlobalLocalReposStore(Entity); + +impl Global for GlobalLocalReposStore {} + +/// Store of the git repositories discovered under a set of scan paths. +pub struct LocalReposStore { + pub roots: Arc>, + /// Git repositories discovered under [`Self::roots`], sorted by path. + pub repos: Arc>, + pub scanning: bool, + scan_dirty: bool, +} + +impl LocalReposStore { + pub fn global(cx: &App) -> Entity { + cx.global::().0.clone() + } + + pub(crate) fn set_global(entity: Entity, cx: &mut App) { + cx.set_global(GlobalLocalReposStore(entity)); + } + + pub fn new(roots: Vec, cx: &mut Context) -> Self { + let weak = cx.entity().downgrade(); + cx.defer(move |cx| { + if let Err(error) = weak.update(cx, |this, cx| this.rescan(cx)) { + log::warn!("local repos store dropped before initial scan could run: {error}"); + } + }); + + Self { + roots: Arc::new(roots), + repos: Arc::new(Vec::new()), + scanning: false, + scan_dirty: false, + } + } + + /// Forget a repository that has just been published to NIP-34. + pub fn remove(&mut self, path: &Path, cx: &mut Context) { + self.repos = Arc::new( + self.repos + .iter() + .filter(|repo| repo.as_path() != path) + .cloned() + .collect(), + ); + cx.notify(); + } + + pub fn rescan(&mut self, cx: &mut Context) { + if self.scanning { + self.scan_dirty = true; + return; + } + + if self.roots.is_empty() { + return; + } + + self.scanning = true; + cx.notify(); + + let roots = self.roots.clone(); + + let work = cx.background_spawn(async move { + let mut repos = Vec::new(); + for root in roots.iter() { + repos.extend(find_git_repos(root)); + } + repos.sort(); + repos.dedup(); + repos + }); + + let task: Task> = cx.spawn(async move |this, cx| { + let repos = work.await; + let again = this.update(cx, |this, cx| { + this.repos = Arc::new(repos); + this.scanning = false; + cx.notify(); + + let dirty = this.scan_dirty; + this.scan_dirty = false; + dirty + })?; + + // Scans requested while this one ran are coalesced into one follow-up scan. + if again { + this.update(cx, |this, cx| this.rescan(cx))?; + } + + Ok(()) + }); + + task.detach(); + } +} diff --git a/crates/signed_state/src/repos.rs b/crates/signed_state/src/repos.rs index be1fca7..3c4758f 100644 --- a/crates/signed_state/src/repos.rs +++ b/crates/signed_state/src/repos.rs @@ -1,116 +1,15 @@ use std::collections::HashMap; -use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; use anyhow::Error; -use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task}; +use gpui::{App, AppContext, Context, Entity, Global, Subscription}; use nostr_sdk::prelude::*; use signed_core::{Announcement, Deletions, RepoAddr, filters, repo_addr}; -use signed_git::find_git_repos; use crate::backend::{Backend, BackendEvent}; use crate::refresh::{RefreshGate, RefreshRequest}; -struct GlobalLocalReposStore(Entity); - -impl Global for GlobalLocalReposStore {} - -/// Store of the git repositories discovered under a set of scan paths. -pub struct LocalReposStore { - pub roots: Arc>, - /// Git repositories discovered under [`Self::roots`], sorted by path. - pub repos: Arc>, - pub scanning: bool, - scan_dirty: bool, -} - -impl LocalReposStore { - pub fn global(cx: &App) -> Entity { - cx.global::().0.clone() - } - - pub(crate) fn set_global(entity: Entity, cx: &mut App) { - cx.set_global(GlobalLocalReposStore(entity)); - } - - pub fn new(roots: Vec, cx: &mut Context) -> Self { - let weak = cx.entity().downgrade(); - cx.defer(move |cx| { - if let Err(error) = weak.update(cx, |this, cx| this.rescan(cx)) { - log::warn!("local repos store dropped before initial scan could run: {error}"); - } - }); - - Self { - roots: Arc::new(roots), - repos: Arc::new(Vec::new()), - scanning: false, - scan_dirty: false, - } - } - - /// Forget a repository that has just been published to NIP-34. - pub fn remove(&mut self, path: &Path, cx: &mut Context) { - self.repos = Arc::new( - self.repos - .iter() - .filter(|repo| repo.as_path() != path) - .cloned() - .collect(), - ); - cx.notify(); - } - - pub fn rescan(&mut self, cx: &mut Context) { - if self.scanning { - self.scan_dirty = true; - return; - } - - if self.roots.is_empty() { - return; - } - - self.scanning = true; - cx.notify(); - - let roots = self.roots.clone(); - - let work = cx.background_spawn(async move { - let mut repos = Vec::new(); - for root in roots.iter() { - repos.extend(find_git_repos(root)); - } - repos.sort(); - repos.dedup(); - repos - }); - - let task: Task> = cx.spawn(async move |this, cx| { - let repos = work.await; - let again = this.update(cx, |this, cx| { - this.repos = Arc::new(repos); - this.scanning = false; - cx.notify(); - - let dirty = this.scan_dirty; - this.scan_dirty = false; - dirty - })?; - - // Scans requested while this one ran are coalesced into one follow-up scan. - if again { - this.update(cx, |this, cx| this.rescan(cx))?; - } - - Ok(()) - }); - - task.detach(); - } -} - /// How far back activity events count toward a repository's last activity. const ACTIVITY_WINDOW: Duration = Duration::from_secs(90 * 86_400);