update
This commit is contained in:
@@ -64,10 +64,17 @@ struct Remembered {
|
||||
}
|
||||
|
||||
/// Global store of local-checkout associations and per-checkout statuses.
|
||||
///
|
||||
/// Readers (the sidebar rows, the repository panels) observe this store and
|
||||
/// derive what they display from their own snapshots, so publishing needs no
|
||||
/// fine-grained entities: the store notifies when a slice changed and each
|
||||
/// reader re-derives only what it shows.
|
||||
pub struct CheckoutsStore {
|
||||
/// Checkout paths per announced repository.
|
||||
by_repo: HashMap<RepoAddr, Vec<PathBuf>>,
|
||||
/// Ready-to-contribute statuses of the requested repositories.
|
||||
///
|
||||
/// Those are the repository detail panels currently open.
|
||||
statuses: HashMap<RepoAddr, Vec<CheckoutStatus>>,
|
||||
/// Repositories whose statuses are recomputed on every input change.
|
||||
///
|
||||
@@ -77,7 +84,7 @@ pub struct CheckoutsStore {
|
||||
///
|
||||
/// The sidebar rows of the user's own repositories and their detail panels.
|
||||
push_requested: HashSet<RepoAddr>,
|
||||
/// Ready-to-push statuses of the requested own repositories.
|
||||
/// The ready-to-push statuses of the requested own repositories.
|
||||
push_statuses: HashMap<RepoAddr, Vec<CheckoutStatus>>,
|
||||
/// Last announced head branch per requested repository.
|
||||
///
|
||||
@@ -85,8 +92,8 @@ pub struct CheckoutsStore {
|
||||
requested_head: HashMap<RepoAddr, Option<String>>,
|
||||
/// Refresh coalescing, see [`RefreshGate`].
|
||||
refresh: RefreshGate,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl CheckoutsStore {
|
||||
@@ -127,8 +134,9 @@ impl CheckoutsStore {
|
||||
this.status_requested.clear();
|
||||
this.push_requested.clear();
|
||||
this.requested_head.clear();
|
||||
this.statuses = HashMap::new();
|
||||
this.push_statuses = HashMap::new();
|
||||
this.statuses.clear();
|
||||
this.push_statuses.clear();
|
||||
cx.notify();
|
||||
this.refresh(cx);
|
||||
}
|
||||
}));
|
||||
@@ -142,8 +150,8 @@ impl CheckoutsStore {
|
||||
push_statuses: HashMap::new(),
|
||||
requested_head: HashMap::new(),
|
||||
refresh: RefreshGate::default(),
|
||||
_subscriptions: subscriptions,
|
||||
tasks: Vec::new(),
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
|
||||
if !cfg!(target_arch = "wasm32") {
|
||||
@@ -217,7 +225,7 @@ impl CheckoutsStore {
|
||||
/// The ready-to-contribute statuses of `addr`.
|
||||
///
|
||||
/// Empty while none are known or nothing is ahead.
|
||||
pub fn statuses_of(&self, addr: &RepoAddr) -> Vec<CheckoutStatus> {
|
||||
pub fn ready_statuses_of(&self, addr: &RepoAddr) -> Vec<CheckoutStatus> {
|
||||
self.statuses.get(addr).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -228,13 +236,20 @@ impl CheckoutsStore {
|
||||
}
|
||||
|
||||
/// The ready-to-push statuses of `addr`.
|
||||
/// Only meaningful for repositories announced by the signed-in user.
|
||||
///
|
||||
/// Empty while none are known or nothing is unpushed.
|
||||
pub fn push_statuses_of(&self, addr: &RepoAddr) -> Vec<CheckoutStatus> {
|
||||
self.push_statuses.get(addr).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The number of unpushed commits for a repository.
|
||||
pub fn unpushed(&self, addr: &RepoAddr) -> usize {
|
||||
self.push_statuses
|
||||
.get(addr)
|
||||
.map(|list| list.iter().map(|status| status.ahead as usize).sum())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Re-resolve the associations and the requested statuses.
|
||||
///
|
||||
/// Requests arriving while a pass runs fold into a follow-up.
|
||||
@@ -362,10 +377,20 @@ impl CheckoutsStore {
|
||||
};
|
||||
|
||||
let again = this.update(cx, |this, cx| {
|
||||
let associations_changed = this.by_repo != associations;
|
||||
let statuses_changed = this.statuses != statuses;
|
||||
let push_statuses_changed = this.push_statuses != push_statuses;
|
||||
|
||||
this.by_repo = associations;
|
||||
this.statuses = statuses;
|
||||
this.push_statuses = push_statuses;
|
||||
cx.notify();
|
||||
|
||||
// Poll cycles and identity re-requests recompute the same maps
|
||||
// over and over. Notify only when something actually changed,
|
||||
// so observers skip the no-op heartbeats.
|
||||
if associations_changed || statuses_changed || push_statuses_changed {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
this.refresh.finish()
|
||||
})?;
|
||||
@@ -378,6 +403,7 @@ impl CheckoutsStore {
|
||||
this.update(cx, |this, cx| {
|
||||
if poll && this.refresh.idle() {
|
||||
this.refresh.debounce();
|
||||
|
||||
// Open panels get the fast cadence.
|
||||
// Each cycle fetches every watched checkout's remote.
|
||||
let delay = if this.status_requested.is_empty() {
|
||||
|
||||
@@ -42,7 +42,7 @@ pub fn init(
|
||||
let entity = cx.new(|cx| Backend::new(client, signer, cx));
|
||||
Backend::set_global(entity.clone(), cx);
|
||||
ProfileStore::set_global(cx.new(ProfileStore::new), cx);
|
||||
RepoListStore::set_global(cx.new(|cx| RepoListStore::new(None, cx)), cx);
|
||||
RepoListStore::set_global(cx.new(RepoListStore::new), cx);
|
||||
GitStore::set_global(repos_root, cx);
|
||||
LocalReposStore::set_global(cx.new(|cx| LocalReposStore::new(scan_paths, cx)), cx);
|
||||
CheckoutsStore::set_global(cx.new(CheckoutsStore::new), cx);
|
||||
@@ -57,7 +57,7 @@ pub fn init(cx: &mut App) -> Entity<Backend> {
|
||||
let entity = cx.new(|cx| Backend::new(client, signer, cx));
|
||||
Backend::set_global(entity.clone(), cx);
|
||||
ProfileStore::set_global(cx.new(ProfileStore::new), cx);
|
||||
RepoListStore::set_global(cx.new(|cx| RepoListStore::new(None, cx)), cx);
|
||||
RepoListStore::set_global(cx.new(RepoListStore::new), cx);
|
||||
GitStore::set_global(PathBuf::new(), cx);
|
||||
LocalReposStore::set_global(cx.new(|cx| LocalReposStore::new(Vec::new(), cx)), cx);
|
||||
CheckoutsStore::set_global(cx.new(|cx| CheckoutsStore::new(cx)), cx);
|
||||
|
||||
@@ -42,7 +42,7 @@ impl RepoActivityCounts {
|
||||
}
|
||||
}
|
||||
|
||||
/// Store listing repository announcements, global discovery or per-author.
|
||||
/// Store listing the discovered repository announcements, newest first.
|
||||
pub struct RepoListStore {
|
||||
/// Shared so views can clone the list per frame without a deep copy.
|
||||
pub announcements: Arc<Vec<Announcement>>,
|
||||
@@ -53,7 +53,6 @@ pub struct RepoListStore {
|
||||
///
|
||||
/// Used for the Popular ranking of the explore list.
|
||||
pub counts: Arc<HashMap<RepoAddr, RepoActivityCounts>>,
|
||||
author: Option<PublicKey>,
|
||||
/// Refresh coalescing, see [`RefreshGate`].
|
||||
refresh: RefreshGate,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
@@ -61,7 +60,7 @@ pub struct RepoListStore {
|
||||
}
|
||||
|
||||
impl RepoListStore {
|
||||
/// Retrieve the global explore store.
|
||||
/// Retrieve the global repository list store.
|
||||
pub fn global(cx: &App) -> Entity<Self> {
|
||||
cx.global::<GlobalRepoListStore>().0.clone()
|
||||
}
|
||||
@@ -70,8 +69,8 @@ impl RepoListStore {
|
||||
cx.set_global(GlobalRepoListStore(entity));
|
||||
}
|
||||
|
||||
/// Create a store. If `author` is `None`, all announcements are listed.
|
||||
pub fn new(author: Option<PublicKey>, cx: &mut Context<Self>) -> Self {
|
||||
/// Create the store listing all announcements.
|
||||
pub fn new(cx: &mut Context<Self>) -> Self {
|
||||
let backend = Backend::global(cx);
|
||||
|
||||
let subscription = cx.subscribe(&backend, |this, _backend, event, cx| {
|
||||
@@ -87,14 +86,11 @@ impl RepoListStore {
|
||||
} else {
|
||||
let is_announcement = update.kind == Kind::GitRepoAnnouncement;
|
||||
let is_repo_state = update.kind == Kind::RepoState;
|
||||
let tracked = is_announcement || is_repo_state;
|
||||
tracked && this.author.is_none_or(|a| a == update.author)
|
||||
is_announcement || is_repo_state
|
||||
}
|
||||
}
|
||||
BackendEvent::Published(event) => {
|
||||
let kind_match = event.kind == Kind::GitRepoAnnouncement;
|
||||
let author_match = this.author.is_none_or(|a| a == event.pubkey);
|
||||
let announcement = kind_match && author_match;
|
||||
let announcement = event.kind == Kind::GitRepoAnnouncement;
|
||||
|
||||
// Locally published deletions are already in the local database.
|
||||
// Refresh so they take effect immediately, like relay deletions.
|
||||
@@ -116,7 +112,6 @@ impl RepoListStore {
|
||||
announcements: Arc::new(Vec::new()),
|
||||
last_activity: Arc::new(HashMap::new()),
|
||||
counts: Arc::new(HashMap::new()),
|
||||
author,
|
||||
refresh: RefreshGate::default(),
|
||||
_subscription: subscription,
|
||||
tasks: Vec::new(),
|
||||
@@ -129,6 +124,15 @@ impl RepoListStore {
|
||||
store
|
||||
}
|
||||
|
||||
/// The announcements of `user`, newest first.
|
||||
pub fn announcements_of(&self, user: &PublicKey) -> Vec<Announcement> {
|
||||
self.announcements
|
||||
.iter()
|
||||
.filter(|a| a.owner == *user)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Track a spawned task, pruning finished tasks first.
|
||||
///
|
||||
/// Keeps the store's task list bounded by the number of in-flight tasks.
|
||||
@@ -140,14 +144,9 @@ impl RepoListStore {
|
||||
/// Negentropy-sync announcements with the bootstrap relays.
|
||||
fn subscribe_remote(&mut self, cx: &mut Context<Self>) {
|
||||
let backend = Backend::global(cx);
|
||||
let author = self.author;
|
||||
|
||||
backend.update(cx, |backend, cx| {
|
||||
let filter = match author {
|
||||
Some(a) => filters::announcements_by(a),
|
||||
None => filters::all_announcements(),
|
||||
};
|
||||
backend.sync_bootstrap(filter, cx);
|
||||
backend.sync_bootstrap(filters::all_announcements(), cx);
|
||||
// Deletion requests, NIP-09/62, must be known before any announcement is shown.
|
||||
backend.sync_bootstrap(filters::deletions(), cx);
|
||||
});
|
||||
@@ -187,15 +186,11 @@ impl RepoListStore {
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
let client = backend.read(cx).client();
|
||||
let author = self.author;
|
||||
|
||||
let work = cx.background_spawn(async move {
|
||||
let filter = match author {
|
||||
Some(a) => filters::announcements_by(a),
|
||||
None => filters::all_announcements(),
|
||||
};
|
||||
|
||||
let filter = filters::all_announcements();
|
||||
let events = client.database().query(filter).await?;
|
||||
|
||||
let deletion_events = client.database().query(filters::deletions()).await?;
|
||||
let deletions = Deletions::from_events(deletion_events);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user