update community
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use concord::cord02::ControlFold;
|
||||
use concord::cord02::{ControlFold, ImageRef};
|
||||
use concord::store::{ChannelKeyRef, CommunityState};
|
||||
use concord::{ChannelId, CommunityId, Epoch};
|
||||
use gpui::{AppContext, Context, EventEmitter, Task};
|
||||
@@ -45,8 +46,11 @@ pub struct Community {
|
||||
state: CommunityState,
|
||||
control: ControlFold,
|
||||
members: BTreeSet<PublicKey>,
|
||||
icon: Option<PathBuf>,
|
||||
icon_ref: Option<ImageRef>,
|
||||
dirty: bool,
|
||||
refresh_task: Option<Task<Result<()>>>,
|
||||
icon_task: Option<Task<Result<()>>>,
|
||||
}
|
||||
|
||||
impl EventEmitter<CommunityEvent> for Community {}
|
||||
@@ -57,8 +61,11 @@ impl Community {
|
||||
state,
|
||||
control: ControlFold::default(),
|
||||
members: BTreeSet::new(),
|
||||
icon: None,
|
||||
icon_ref: None,
|
||||
dirty: false,
|
||||
refresh_task: None,
|
||||
icon_task: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,16 +78,25 @@ impl Community {
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
match &self.control.community {
|
||||
Some(metadata) => metadata.name.clone(),
|
||||
None => self.state.id.to_hex(),
|
||||
if let Some(metadata) = &self.control.community {
|
||||
return metadata.name.clone();
|
||||
}
|
||||
|
||||
self.state
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.state.id.to_hex())
|
||||
}
|
||||
|
||||
pub fn control(&self) -> &ControlFold {
|
||||
&self.control
|
||||
}
|
||||
|
||||
/// The community's icon, once downloaded and decrypted into a cache file.
|
||||
pub fn icon(&self) -> Option<PathBuf> {
|
||||
self.icon.clone()
|
||||
}
|
||||
|
||||
pub fn members(&self) -> &BTreeSet<PublicKey> {
|
||||
&self.members
|
||||
}
|
||||
@@ -121,6 +137,7 @@ impl Community {
|
||||
self.state = snapshot.state;
|
||||
self.control = snapshot.control;
|
||||
self.members = snapshot.members;
|
||||
self.load_icon(cx);
|
||||
cx.emit(CommunityEvent::Updated(self.state.id));
|
||||
cx.notify();
|
||||
}
|
||||
@@ -133,4 +150,37 @@ impl Community {
|
||||
self.refresh(cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the folded icon into a local file.
|
||||
fn load_icon(&mut self, cx: &mut Context<Self>) {
|
||||
let icon = self
|
||||
.control
|
||||
.community
|
||||
.as_ref()
|
||||
.and_then(|metadata| metadata.icon.clone());
|
||||
|
||||
if self.icon_ref == icon {
|
||||
return;
|
||||
}
|
||||
|
||||
self.icon_ref = icon.clone();
|
||||
self.icon = None;
|
||||
|
||||
let Some(icon) = icon else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.icon_task = Some(cx.spawn(async move |this, cx| {
|
||||
match sync::resolve_icon(&icon, cx).await {
|
||||
Ok(path) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.icon = Some(path);
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
Err(error) => log::warn!("community icon: {error}"),
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +357,6 @@ async fn subscribe(
|
||||
relays: &[RelayUrl],
|
||||
filter: Filter,
|
||||
) -> Result<()> {
|
||||
log::info!("community {id}: subscribing to {relays:?}");
|
||||
client.unsubscribe(id).await?;
|
||||
|
||||
for url in relays {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use concord::cord01::KIND_WRAP;
|
||||
use concord::cord02::list::{CommunityList, KIND_COMMUNITY_LIST};
|
||||
use concord::cord02::{self, ControlFold};
|
||||
use concord::cord02::{self, ControlFold, ImageRef};
|
||||
use concord::cord04::AuthorityCitation;
|
||||
use concord::cord04::roles::{Permissions, citation_ok};
|
||||
use concord::derive::{
|
||||
@@ -11,6 +12,7 @@ use concord::derive::{
|
||||
};
|
||||
use concord::store::{self, CommunityState};
|
||||
use concord::{ChannelId, CommunityId, Epoch, GroupKey};
|
||||
use gpui::AsyncApp;
|
||||
use nostr_sdk::prelude::*;
|
||||
use state::UniversalSigner;
|
||||
|
||||
@@ -42,6 +44,15 @@ pub fn planes(state: &CommunityState) -> Result<Vec<Plane>> {
|
||||
});
|
||||
}
|
||||
|
||||
if state.control_pks.is_empty() {
|
||||
let group = control_group_key(&state.community_root, &state.id, state.root_epoch)?;
|
||||
planes.push(Plane {
|
||||
kind: PlaneKind::Control(state.root_epoch),
|
||||
address: group.pk(),
|
||||
group,
|
||||
});
|
||||
}
|
||||
|
||||
let group = guestbook_group_key(&state.community_root, &state.id, state.root_epoch)?;
|
||||
planes.push(Plane {
|
||||
kind: PlaneKind::Guestbook,
|
||||
@@ -85,6 +96,12 @@ pub fn community_of(subscription_id: &SubscriptionId) -> Option<CommunityId> {
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Download and decrypt a community icon into a content-addressed cache file.
|
||||
pub async fn resolve_icon(icon: &ImageRef, cx: &AsyncApp) -> Result<PathBuf> {
|
||||
let url = Url::parse(&icon.url).context("community icon url")?;
|
||||
state::download_and_decrypt_to_cache(&url, &icon.key, &icon.nonce, &icon.hash, cx).await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Snapshot {
|
||||
pub state: CommunityState,
|
||||
@@ -317,6 +334,10 @@ fn refresh(mut held: CommunityState, fresh: CommunityState) -> CommunityState {
|
||||
held.control_root = fresh.control_root;
|
||||
}
|
||||
|
||||
if let Some(name) = fresh.name {
|
||||
held.name = Some(name);
|
||||
}
|
||||
|
||||
for (epoch, address) in fresh.control_pks {
|
||||
held.control_pks.insert(epoch, address);
|
||||
}
|
||||
@@ -517,6 +538,7 @@ mod tests {
|
||||
|
||||
let state = CommunityState {
|
||||
id: CommunityId::from_bytes([0x42; 32]),
|
||||
name: Some("Anime and Manga".to_owned()),
|
||||
owner,
|
||||
owner_salt: [0x01; 32],
|
||||
community_root: [0x02; 32],
|
||||
@@ -548,9 +570,6 @@ mod tests {
|
||||
|
||||
let planes = planes(&state).expect("planes");
|
||||
|
||||
// Control at the root epoch, the guestbook, and the public channel. The
|
||||
// private channel is skipped: its address derives from the granted key,
|
||||
// not the community_root.
|
||||
assert_eq!(planes.len(), 3);
|
||||
assert!(planes.iter().any(|plane| plane.address == control_pk));
|
||||
assert!(
|
||||
@@ -580,6 +599,7 @@ mod tests {
|
||||
fn held(id: CommunityId, control_pk: PublicKey) -> CommunityState {
|
||||
CommunityState {
|
||||
id,
|
||||
name: Some("Anime and Manga".to_owned()),
|
||||
owner: Keys::generate().public_key(),
|
||||
owner_salt: [0x01; 32],
|
||||
community_root: [0x02; 32],
|
||||
|
||||
Reference in New Issue
Block a user