Detach Background Tasks Directly
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as AnyhowContext, Error, anyhow, bail};
|
||||
use bitcoin_hashes::sha1::Hash as Sha1Hash;
|
||||
@@ -35,9 +33,6 @@ pub const BOOTSTRAP_RELAYS: [&str; 4] = [
|
||||
/// Relays used to index the user's NIP-65 relay list.
|
||||
pub const INDEXER_RELAYS: [&str; 2] = ["wss://indexer.coracle.social", "wss://user.kindpag.es"];
|
||||
|
||||
/// How long an identical fetch or sync request is suppressed after it started.
|
||||
const FETCH_DEDUP_WINDOW: Duration = Duration::from_secs(5 * 60);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BackendEvent {
|
||||
/// User has no signer configured.
|
||||
@@ -82,11 +77,8 @@ pub struct Backend {
|
||||
sync_progress: Option<(u64, u64)>,
|
||||
/// True when the stored credential is NIP-49 encrypted.
|
||||
passphrase_required: bool,
|
||||
/// Fingerprints of recently started fetches and syncs, a relay plus filter set.
|
||||
recent_fetches: HashMap<u64, Instant>,
|
||||
/// Repositories with a push in flight, mirror or checkout based.
|
||||
pushing_repos: Arc<Mutex<HashSet<RepoAddr>>>,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
}
|
||||
|
||||
struct GlobalBackend(Entity<Backend>);
|
||||
@@ -124,7 +116,7 @@ impl Backend {
|
||||
pub(crate) fn new(client: Client, signer: UniversalSigner, cx: &mut Context<Self>) -> Self {
|
||||
let pump_client = client.clone();
|
||||
|
||||
let pump = cx.spawn(async move |this, cx| {
|
||||
let pump: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let mut notifications = pump_client.notifications();
|
||||
|
||||
while let Some(notification) = notifications.next().await {
|
||||
@@ -145,29 +137,21 @@ impl Backend {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
pump.detach();
|
||||
|
||||
let mut this = Self {
|
||||
client,
|
||||
signer,
|
||||
current_user: None,
|
||||
sync_progress: None,
|
||||
passphrase_required: false,
|
||||
recent_fetches: HashMap::new(),
|
||||
pushing_repos: Arc::new(Mutex::new(HashSet::new())),
|
||||
tasks: vec![pump],
|
||||
};
|
||||
|
||||
this.bootstrap(cx);
|
||||
this
|
||||
}
|
||||
|
||||
/// Track a spawned task, pruning finished tasks first.
|
||||
///
|
||||
/// Keeps the store's task list bounded by the number of in-flight tasks.
|
||||
fn push_task(&mut self, task: Task<Result<(), Error>>) {
|
||||
self.tasks.retain(|task| !task.is_ready());
|
||||
self.tasks.push(task);
|
||||
}
|
||||
|
||||
/// Bootstrap the client.
|
||||
///
|
||||
/// Restore the saved session, if any.
|
||||
@@ -188,7 +172,7 @@ impl Backend {
|
||||
Ok::<(), Error>(())
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let notify_task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
match task.await {
|
||||
Ok(()) => {
|
||||
this.update(cx, |_this, cx| cx.notify())?;
|
||||
@@ -198,7 +182,8 @@ impl Backend {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
notify_task.detach();
|
||||
|
||||
self.restore_session(cx);
|
||||
}
|
||||
@@ -216,7 +201,7 @@ impl Backend {
|
||||
|
||||
let user = cx.read_credentials(USER_KEYRING);
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let content = match user.await {
|
||||
Ok(Some((_username, secret))) => String::from_utf8(secret)?,
|
||||
_ => {
|
||||
@@ -264,7 +249,8 @@ impl Backend {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Decrypt the NIP-49 keyring credential with the given passphrase.
|
||||
@@ -982,14 +968,15 @@ impl Backend {
|
||||
let pubkey = keys.public_key().to_hex();
|
||||
let write = cx.write_credentials(USER_KEYRING, &pubkey, nsec.as_bytes());
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = write.await {
|
||||
this.update(cx, |_, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||
return Ok(());
|
||||
}
|
||||
this.update(cx, |this, cx| this.set_signer(keys, cx))?;
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Login with a `bunker://...` URI, NIP-46.
|
||||
@@ -1008,7 +995,7 @@ impl Backend {
|
||||
let credential = with_master_key(&uri_string, &keys);
|
||||
let write = cx.write_credentials(USER_KEYRING, "bunker", credential.as_bytes());
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let result = async {
|
||||
let mut signer = NostrConnect::new(
|
||||
connect_uri,
|
||||
@@ -1033,14 +1020,15 @@ impl Backend {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Remove the saved credential and reset to an anonymous session.
|
||||
pub fn logout(&mut self, cx: &mut Context<Self>) {
|
||||
let delete = cx.delete_credentials(USER_KEYRING);
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
delete.await.ok();
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -1053,14 +1041,15 @@ impl Backend {
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Fetch the user's grasp list and add the listed grasp servers as relays.
|
||||
fn bootstrap_user(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
|
||||
let client = self.client.clone();
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let result = async {
|
||||
let events: Vec<Event> = client
|
||||
.fetch_events(filters::grasp_list(public_key))
|
||||
@@ -1082,7 +1071,8 @@ impl Backend {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Get the nostr client.
|
||||
@@ -1123,7 +1113,7 @@ impl Backend {
|
||||
<T as AsyncSignEvent>::Error: std::error::Error + Send + Sync + 'static,
|
||||
<T as AsyncNip44>::Error: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
match new_signer.get_public_key_async().await {
|
||||
Ok(public_key) => {
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -1144,7 +1134,7 @@ impl Backend {
|
||||
|
||||
Ok(())
|
||||
});
|
||||
self.push_task(task);
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Add relays and connect to them.
|
||||
@@ -1159,7 +1149,7 @@ impl Backend {
|
||||
Ok::<(), Error>(())
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let notify_task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
match task.await {
|
||||
Ok(()) => {
|
||||
this.update(cx, |_this, cx| cx.notify())?;
|
||||
@@ -1169,74 +1159,49 @@ impl Backend {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Whether an identical fetch started within [`FETCH_DEDUP_WINDOW`] is still recent.
|
||||
///
|
||||
/// Records the fingerprint when returning `false`, pruning expired entries first.
|
||||
fn fetch_recently_started(&mut self, fingerprint: u64) -> bool {
|
||||
self.recent_fetches
|
||||
.retain(|_, started| started.elapsed() < FETCH_DEDUP_WINDOW);
|
||||
if self.recent_fetches.contains_key(&fingerprint) {
|
||||
return true;
|
||||
}
|
||||
self.recent_fetches.insert(fingerprint, Instant::now());
|
||||
false
|
||||
});
|
||||
notify_task.detach();
|
||||
}
|
||||
|
||||
/// Connect to a repository's announced relays, its NIP-34 `relays` tag.
|
||||
///
|
||||
/// Callers are responsible for not repeating this for relays they already
|
||||
/// connected, e.g. `RepoStore::repo_relays`.
|
||||
pub fn connect_repo_relays(
|
||||
&mut self,
|
||||
relays: Vec<RelayUrl>,
|
||||
filters: Vec<Filter>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let relay_strs: Vec<&str> = relays.iter().map(|url| url.as_str()).collect();
|
||||
let fingerprint = fetch_fingerprint(&relay_strs, &filters);
|
||||
if self.fetch_recently_started(fingerprint) {
|
||||
log::debug!("skipping duplicate repo relay fetch");
|
||||
return;
|
||||
}
|
||||
|
||||
let client = self.client.clone();
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |_this, _cx| {
|
||||
if let Err(e) = connect_repo_relays(&client, relays, filters).await {
|
||||
log::warn!("repo relay fetch failed: {e}");
|
||||
// Allow an immediate retry after a failure.
|
||||
this.update(cx, |this, _cx| {
|
||||
this.recent_fetches.remove(&fingerprint);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// One-shot subscription on the bootstrap relays only.
|
||||
pub fn subscribe_bootstrap(&mut self, filters: Vec<Filter>, cx: &mut Context<Self>) {
|
||||
let client = self.client.clone();
|
||||
|
||||
let task =
|
||||
let fetch =
|
||||
cx.background_spawn(async move { subscribe_bootstrap_only(&client, filters).await });
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = task.await {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = fetch.await {
|
||||
this.update(cx, |_this, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Negentropy-sync the given filter against the bootstrap relays.
|
||||
pub fn sync_bootstrap(&mut self, filter: Filter, cx: &mut Context<Self>) {
|
||||
let fingerprint = fetch_fingerprint(&BOOTSTRAP_RELAYS, std::slice::from_ref(&filter));
|
||||
if self.fetch_recently_started(fingerprint) {
|
||||
log::debug!("skipping duplicate bootstrap sync");
|
||||
return;
|
||||
}
|
||||
|
||||
let client = self.client.clone();
|
||||
|
||||
self.sync_progress = Some((0, 0));
|
||||
@@ -1244,7 +1209,7 @@ impl Backend {
|
||||
|
||||
let (tx, mut rx) = SyncProgress::channel();
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let progress_task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let mut last_percent: u64 = 0;
|
||||
|
||||
while rx.changed().await.is_ok() {
|
||||
@@ -1270,15 +1235,16 @@ impl Backend {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
progress_task.detach();
|
||||
|
||||
let task = cx.background_spawn(async move {
|
||||
let sync = cx.background_spawn(async move {
|
||||
let opts = SyncOptions::default().progress(tx);
|
||||
sync_bootstrap_only(&client, filter, opts).await
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
match task.await {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
match sync.await {
|
||||
Ok(summary) => {
|
||||
log::debug!(
|
||||
"sync done: {} received, {} sent",
|
||||
@@ -1294,14 +1260,13 @@ impl Backend {
|
||||
Err(e) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.sync_progress = None;
|
||||
// Allow an immediate retry after a failure.
|
||||
this.recent_fetches.remove(&fingerprint);
|
||||
cx.emit(BackendEvent::error(e.to_string()))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Sign, broadcast and locally store an event.
|
||||
@@ -1361,17 +1326,18 @@ impl Backend {
|
||||
|
||||
/// Sign, broadcast and store an event without awaiting the result.
|
||||
fn send_fire_and_forget(&mut self, builder: EventBuilder, cx: &mut Context<Self>) {
|
||||
let task = self.send(builder, cx);
|
||||
let publish = self.send(builder, cx);
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = task.await {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = publish.await {
|
||||
this.update(cx, |_this, cx| {
|
||||
cx.emit(BackendEvent::error(e.to_string()));
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Publish NIP-09 deletions for `events`, best-effort.
|
||||
@@ -1387,14 +1353,15 @@ impl Backend {
|
||||
tags.push(Tag::parse(["k", &event.kind.to_string()]).expect("valid kind tag"));
|
||||
}
|
||||
|
||||
let task = self.send(EventBuilder::new(Kind::EventDeletion, "").tags(tags), cx);
|
||||
let publish = self.send(EventBuilder::new(Kind::EventDeletion, "").tags(tags), cx);
|
||||
|
||||
self.push_task(cx.spawn(async move |_this, _cx| {
|
||||
if let Err(e) = task.await {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |_this, _cx| {
|
||||
if let Err(e) = publish.await {
|
||||
log::warn!("failed to retract repository events: {e}");
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1417,21 +1384,6 @@ async fn broadcast_event(client: &Client, event: &Event) -> Result<Event, Error>
|
||||
Ok(event.clone())
|
||||
}
|
||||
|
||||
/// Fingerprint of a relay and filter set, for fetch dedup.
|
||||
///
|
||||
/// Relays and filters are sorted first, so the fingerprint is order-independent.
|
||||
fn fetch_fingerprint(relays: &[&str], filters: &[Filter]) -> u64 {
|
||||
let mut relays: Vec<&str> = relays.to_vec();
|
||||
relays.sort_unstable();
|
||||
let mut filters: Vec<&Filter> = filters.iter().collect();
|
||||
filters.sort_unstable();
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
relays.hash(&mut hasher);
|
||||
filters.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Add the given relays, connect and fetch the filters.
|
||||
async fn connect_repo_relays(
|
||||
client: &Client,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use anyhow::Error;
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription};
|
||||
use nostr::prelude::*;
|
||||
use settings::{CheckoutRecord, SettingsStore};
|
||||
use signed_core::{Announcement, RepoAddr};
|
||||
@@ -105,7 +105,6 @@ pub struct CheckoutsStore {
|
||||
/// The local pass runs a full pass again once this is older than the
|
||||
/// reconciliation cadence, so remote moves still land.
|
||||
last_full_sync: Option<Instant>,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -165,7 +164,6 @@ impl CheckoutsStore {
|
||||
refresh: RefreshGate::default(),
|
||||
local_pending: false,
|
||||
last_full_sync: None,
|
||||
tasks: Vec::new(),
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
|
||||
@@ -176,14 +174,6 @@ impl CheckoutsStore {
|
||||
store
|
||||
}
|
||||
|
||||
/// Track a spawned task, pruning finished tasks first.
|
||||
///
|
||||
/// Keeps the store's task list bounded by the number of in-flight tasks.
|
||||
fn push_task(&mut self, task: Task<Result<(), Error>>) {
|
||||
self.tasks.retain(|task| !task.is_ready());
|
||||
self.tasks.push(task);
|
||||
}
|
||||
|
||||
/// Remember a successful local-checkout use.
|
||||
pub fn record(&mut self, path: PathBuf, addr: RepoAddr, cx: &mut Context<Self>) {
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
@@ -302,12 +292,11 @@ impl CheckoutsStore {
|
||||
return;
|
||||
}
|
||||
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
|
||||
this.update(cx, |this, cx| this.run_refresh(cx))
|
||||
});
|
||||
|
||||
self.push_task(task);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// One full resolve and apply cycle, the debounced entry point.
|
||||
@@ -388,7 +377,7 @@ impl CheckoutsStore {
|
||||
Ok::<_, Error>((associations, statuses, push_statuses))
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let (associations, statuses, push_statuses) = match work.await {
|
||||
Ok(results) => results,
|
||||
Err(_) => {
|
||||
@@ -435,7 +424,8 @@ impl CheckoutsStore {
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Schedule the fast local status pass, unless one is already pending.
|
||||
@@ -449,15 +439,14 @@ impl CheckoutsStore {
|
||||
}
|
||||
self.local_pending = true;
|
||||
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(LOCAL_POLL).await;
|
||||
this.update(cx, |this, cx| {
|
||||
this.local_pending = false;
|
||||
this.local_tick(cx);
|
||||
})
|
||||
});
|
||||
|
||||
self.push_task(task);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// The fast local status pass.
|
||||
@@ -525,7 +514,7 @@ impl CheckoutsStore {
|
||||
Ok::<_, Error>((statuses, push_statuses))
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: gpui::Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let Ok((statuses, push_statuses)) = work.await else {
|
||||
// Git reads are best-effort, keep the last results.
|
||||
return Ok(());
|
||||
@@ -550,7 +539,8 @@ impl CheckoutsStore {
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Error;
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Task};
|
||||
use gpui::{App, AppContext, Context, Entity, Global};
|
||||
use signed_git::find_git_repos;
|
||||
|
||||
struct GlobalLocalReposStore(Entity<LocalReposStore>);
|
||||
@@ -19,7 +19,6 @@ pub struct LocalReposStore {
|
||||
pub scanning: bool,
|
||||
/// A scan was requested while one was already running.
|
||||
scan_dirty: bool,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
}
|
||||
|
||||
impl LocalReposStore {
|
||||
@@ -39,7 +38,6 @@ impl LocalReposStore {
|
||||
repos: Arc::new(Vec::new()),
|
||||
scanning: false,
|
||||
scan_dirty: false,
|
||||
tasks: Vec::new(),
|
||||
};
|
||||
store.rescan(cx);
|
||||
store
|
||||
@@ -81,7 +79,7 @@ impl LocalReposStore {
|
||||
repos
|
||||
});
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let task: gpui::Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let repos = work.await;
|
||||
let again = this.update(cx, |this, cx| {
|
||||
this.repos = Arc::new(repos);
|
||||
@@ -99,6 +97,7 @@ impl LocalReposStore {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ pub struct ProfileStore {
|
||||
seen: RefCell<HashSet<PublicKey>>,
|
||||
/// Sender for queuing fetch requests, batched by a background task.
|
||||
sender: Sender<PublicKey>,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
@@ -114,17 +113,15 @@ impl ProfileStore {
|
||||
let (sender, receiver) = flume::unbounded::<PublicKey>();
|
||||
let entity = cx.entity().downgrade();
|
||||
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
tasks.push(cx.spawn(async move |_this, cx| {
|
||||
cx.spawn(async move |_this, cx| {
|
||||
Self::handle_requests(entity, &client, &receiver, cx).await
|
||||
}));
|
||||
})
|
||||
.detach();
|
||||
|
||||
let mut store = Self {
|
||||
profiles: HashMap::new(),
|
||||
seen: RefCell::new(HashSet::new()),
|
||||
sender,
|
||||
tasks,
|
||||
_subscription: subscription,
|
||||
};
|
||||
|
||||
@@ -132,14 +129,6 @@ impl ProfileStore {
|
||||
store
|
||||
}
|
||||
|
||||
/// Track a spawned task, pruning finished tasks first.
|
||||
///
|
||||
/// Keeps the store's task list bounded by the number of in-flight tasks.
|
||||
fn push_task(&mut self, task: Task<Result<(), Error>>) {
|
||||
self.tasks.retain(|task| !task.is_ready());
|
||||
self.tasks.push(task);
|
||||
}
|
||||
|
||||
/// Get a profile.
|
||||
///
|
||||
/// Returns a placeholder with default metadata. Queues a fetch when the profile is not cached yet.
|
||||
@@ -181,7 +170,7 @@ impl ProfileStore {
|
||||
Ok::<_, Error>(profiles)
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let profiles = work.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -192,7 +181,8 @@ impl ProfileStore {
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Re-read the latest metadata of an author from the local database.
|
||||
@@ -217,7 +207,7 @@ impl ProfileStore {
|
||||
Ok::<_, Error>(profile)
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let profile = work.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -228,7 +218,8 @@ impl ProfileStore {
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Re-read the latest metadata of every requested author from the local database.
|
||||
@@ -273,7 +264,7 @@ impl ProfileStore {
|
||||
Ok::<_, Error>(profiles)
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let profiles = work.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -284,7 +275,8 @@ impl ProfileStore {
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Sync metadata for requested authors in batches, debounced to collect requests.
|
||||
|
||||
@@ -81,7 +81,6 @@ pub struct RepoStore {
|
||||
root_fetches: HashSet<EventId>,
|
||||
/// Refresh coalescing, see [`RefreshGate`].
|
||||
refresh: RefreshGate,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
@@ -148,7 +147,6 @@ impl RepoStore {
|
||||
root_fetches: HashSet::new(),
|
||||
refresh: RefreshGate::default(),
|
||||
_subscription: subscription,
|
||||
tasks: Vec::new(),
|
||||
};
|
||||
|
||||
store.subscribe_remote(cx);
|
||||
@@ -229,14 +227,12 @@ impl RepoStore {
|
||||
return;
|
||||
}
|
||||
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
|
||||
|
||||
this.update(cx, |this, cx| this.run_refresh(cx))
|
||||
});
|
||||
|
||||
self.tasks.retain(|task| !task.is_ready());
|
||||
self.tasks.push(task);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn run_refresh(&mut self, cx: &mut Context<Self>) {
|
||||
@@ -372,9 +368,7 @@ impl RepoStore {
|
||||
))
|
||||
});
|
||||
|
||||
self.tasks.retain(|task| !task.is_ready());
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let (
|
||||
announcement,
|
||||
state,
|
||||
@@ -466,7 +460,8 @@ impl RepoStore {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Resolve the status of a root event, an issue, patch or PR, per NIP-34.
|
||||
@@ -638,7 +633,7 @@ impl RepoStore {
|
||||
.collect()
|
||||
};
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
// The PR references the root patch event.
|
||||
// Viewers can then find the patch without carrying it inline.
|
||||
let root_patch = match publish_patch_series(
|
||||
@@ -822,7 +817,8 @@ impl RepoStore {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Update a pull request.
|
||||
@@ -894,7 +890,7 @@ impl RepoStore {
|
||||
.map(|a| a.clone.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = publish_patch_series(
|
||||
&this,
|
||||
cx,
|
||||
@@ -943,7 +939,8 @@ impl RepoStore {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Set the status of a root event.
|
||||
@@ -1040,7 +1037,7 @@ impl RepoStore {
|
||||
Ok::<_, Error>(applied)
|
||||
});
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
match apply.await {
|
||||
Ok(applied) => {
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -1062,7 +1059,8 @@ impl RepoStore {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// The latest announcement of this repository,
|
||||
@@ -1335,17 +1333,18 @@ impl RepoStore {
|
||||
self.last_error = None;
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
let task = backend.update(cx, |backend, cx| backend.send(builder, cx));
|
||||
let publish = backend.update(cx, |backend, cx| backend.send(builder, cx));
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = task.await {
|
||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = publish.await {
|
||||
this.update(cx, |this, cx| {
|
||||
this.last_error = Some(e.to_string());
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
});
|
||||
task.detach();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ 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};
|
||||
|
||||
@@ -55,7 +55,6 @@ pub struct RepoListStore {
|
||||
pub counts: Arc<HashMap<RepoAddr, RepoActivityCounts>>,
|
||||
/// Refresh coalescing, see [`RefreshGate`].
|
||||
refresh: RefreshGate,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
@@ -114,7 +113,6 @@ impl RepoListStore {
|
||||
counts: Arc::new(HashMap::new()),
|
||||
refresh: RefreshGate::default(),
|
||||
_subscription: subscription,
|
||||
tasks: Vec::new(),
|
||||
};
|
||||
|
||||
store.subscribe_remote(cx);
|
||||
@@ -133,14 +131,6 @@ impl RepoListStore {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Track a spawned task, pruning finished tasks first.
|
||||
///
|
||||
/// Keeps the store's task list bounded by the number of in-flight tasks.
|
||||
fn push_task(&mut self, task: Task<Result<(), Error>>) {
|
||||
self.tasks.retain(|task| !task.is_ready());
|
||||
self.tasks.push(task);
|
||||
}
|
||||
|
||||
/// Negentropy-sync announcements with the bootstrap relays.
|
||||
fn subscribe_remote(&mut self, cx: &mut Context<Self>) {
|
||||
let backend = Backend::global(cx);
|
||||
@@ -171,13 +161,12 @@ impl RepoListStore {
|
||||
return;
|
||||
}
|
||||
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
|
||||
|
||||
this.update(cx, |this, cx| this.run_refresh(cx))
|
||||
});
|
||||
|
||||
self.push_task(task);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// One query and apply cycle, the debounced entry point.
|
||||
@@ -294,7 +283,7 @@ impl RepoListStore {
|
||||
Ok::<_, Error>((announcements, last_activity, counts))
|
||||
});
|
||||
|
||||
self.push_task(cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let (announcements, last_activity, counts) = match work.await {
|
||||
Ok(results) => results,
|
||||
// Database errors are transient, keep the last list.
|
||||
@@ -321,6 +310,7 @@ impl RepoListStore {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user