mirror of
https://github.com/aljazceru/notedeck.git
synced 2026-02-21 16:34:19 +01:00
Add Channel and RelayConfig data structures for Slack-like interface
This commit lays the foundation for the Slack-like interface redesign: - Add Channel data structure: represents a channel that filters notes by hashtags - Add ChannelList: manages multiple channels per user - Add ChannelsCache: maps users to their channel lists (stored per-account) - Add RelayConfig: global relay configuration (not tied to user profiles) - Add persistence: save/load channels and relay config to disk - Update Damus app state to include channels_cache and relay_config Next steps: - Create sidebar UI with channel list - Build main chat view with message bubbles - Add thread side panel - Update main layout to use new components
This commit is contained in:
@@ -43,6 +43,8 @@ pub struct Damus {
|
||||
state: DamusState,
|
||||
|
||||
pub decks_cache: DecksCache,
|
||||
pub channels_cache: crate::channels::ChannelsCache,
|
||||
pub relay_config: crate::relay_config::RelayConfig,
|
||||
pub view_state: ViewState,
|
||||
pub drafts: Drafts,
|
||||
pub timeline_cache: TimelineCache,
|
||||
@@ -521,6 +523,29 @@ impl Damus {
|
||||
let jobs = JobsCache::default();
|
||||
let threads = Threads::default();
|
||||
|
||||
// Load or create channels cache
|
||||
let channels_cache = if let Some(channels_cache) = crate::storage::load_channels_cache(
|
||||
app_context.path,
|
||||
app_context.i18n,
|
||||
) {
|
||||
info!("ChannelsCache: loading from disk");
|
||||
channels_cache
|
||||
} else {
|
||||
info!("ChannelsCache: creating new with default channels");
|
||||
crate::channels::ChannelsCache::default_channels_cache(app_context.i18n)
|
||||
};
|
||||
|
||||
// Load or create relay config
|
||||
let relay_config = if let Some(relay_config) = crate::storage::load_relay_config(
|
||||
app_context.path,
|
||||
) {
|
||||
info!("RelayConfig: loading from disk");
|
||||
relay_config
|
||||
} else {
|
||||
info!("RelayConfig: creating new with default relays");
|
||||
crate::relay_config::RelayConfig::default()
|
||||
};
|
||||
|
||||
Self {
|
||||
subscriptions,
|
||||
timeline_cache,
|
||||
@@ -532,6 +557,8 @@ impl Damus {
|
||||
view_state: ViewState::default(),
|
||||
support,
|
||||
decks_cache,
|
||||
channels_cache,
|
||||
relay_config,
|
||||
unrecognized_args,
|
||||
jobs,
|
||||
threads,
|
||||
@@ -564,6 +591,8 @@ impl Damus {
|
||||
pub fn mock<P: AsRef<Path>>(data_path: P) -> Self {
|
||||
let mut i18n = Localization::default();
|
||||
let decks_cache = DecksCache::default_decks_cache(&mut i18n);
|
||||
let channels_cache = crate::channels::ChannelsCache::default_channels_cache(&mut i18n);
|
||||
let relay_config = crate::relay_config::RelayConfig::default();
|
||||
|
||||
let path = DataPath::new(&data_path);
|
||||
let imgcache_dir = path.path(DataPathType::Cache);
|
||||
@@ -583,6 +612,8 @@ impl Damus {
|
||||
support,
|
||||
options,
|
||||
decks_cache,
|
||||
channels_cache,
|
||||
relay_config,
|
||||
unrecognized_args: BTreeSet::default(),
|
||||
jobs: JobsCache::default(),
|
||||
threads: Threads::default(),
|
||||
|
||||
299
crates/notedeck_columns/src/channels.rs
Normal file
299
crates/notedeck_columns/src/channels.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
use std::collections::HashMap;
|
||||
use enostr::Pubkey;
|
||||
use nostrdb::Transaction;
|
||||
use notedeck::{tr, AppContext, Localization, FALLBACK_PUBKEY};
|
||||
use tracing::{error, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
route::{Route, Router},
|
||||
timeline::{TimelineCache, TimelineKind},
|
||||
subscriptions::Subscriptions,
|
||||
};
|
||||
|
||||
/// Represents a single channel (like a Slack channel)
|
||||
/// Each channel filters notes by hashtag(s)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Channel {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub hashtags: Vec<String>,
|
||||
pub timeline_kind: TimelineKind,
|
||||
pub router: Router<Route>,
|
||||
pub unread_count: usize,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new(name: String, hashtags: Vec<String>) -> Self {
|
||||
let id = Uuid::new_v4();
|
||||
let timeline_kind = TimelineKind::Hashtag(hashtags.clone());
|
||||
let router = Router::new(vec![Route::timeline(timeline_kind.clone())]);
|
||||
|
||||
Self {
|
||||
id,
|
||||
name,
|
||||
hashtags,
|
||||
timeline_kind,
|
||||
router,
|
||||
unread_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_id(id: Uuid, name: String, hashtags: Vec<String>) -> Self {
|
||||
let timeline_kind = TimelineKind::Hashtag(hashtags.clone());
|
||||
let router = Router::new(vec![Route::timeline(timeline_kind.clone())]);
|
||||
|
||||
Self {
|
||||
id,
|
||||
name,
|
||||
hashtags,
|
||||
timeline_kind,
|
||||
router,
|
||||
unread_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn router(&self) -> &Router<Route> {
|
||||
&self.router
|
||||
}
|
||||
|
||||
pub fn router_mut(&mut self) -> &mut Router<Route> {
|
||||
&mut self.router
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains all channels for a user
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ChannelList {
|
||||
pub channels: Vec<Channel>,
|
||||
pub selected: usize,
|
||||
}
|
||||
|
||||
impl ChannelList {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
channels: Vec::new(),
|
||||
selected: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_channels(i18n: &mut Localization) -> Self {
|
||||
let mut list = Self::new();
|
||||
|
||||
// Add a default "general" channel
|
||||
list.add_channel(Channel::new(
|
||||
tr!(i18n, "General", "Default channel name").to_string(),
|
||||
vec!["general".to_string()],
|
||||
));
|
||||
|
||||
list
|
||||
}
|
||||
|
||||
pub fn add_channel(&mut self, channel: Channel) {
|
||||
self.channels.push(channel);
|
||||
}
|
||||
|
||||
pub fn remove_channel(&mut self, index: usize) -> Option<Channel> {
|
||||
if index < self.channels.len() && self.channels.len() > 1 {
|
||||
let removed = self.channels.remove(index);
|
||||
|
||||
// Adjust selected index if needed
|
||||
if self.selected >= self.channels.len() {
|
||||
self.selected = self.channels.len() - 1;
|
||||
}
|
||||
|
||||
Some(removed)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_channel(&mut self, index: usize) {
|
||||
if index < self.channels.len() {
|
||||
self.selected = index;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_channel(&self) -> Option<&Channel> {
|
||||
self.channels.get(self.selected)
|
||||
}
|
||||
|
||||
pub fn selected_channel_mut(&mut self) -> Option<&mut Channel> {
|
||||
self.channels.get_mut(self.selected)
|
||||
}
|
||||
|
||||
pub fn num_channels(&self) -> usize {
|
||||
self.channels.len()
|
||||
}
|
||||
|
||||
pub fn get_channel(&self, index: usize) -> Option<&Channel> {
|
||||
self.channels.get(index)
|
||||
}
|
||||
|
||||
pub fn get_channel_mut(&mut self, index: usize) -> Option<&mut Channel> {
|
||||
self.channels.get_mut(index)
|
||||
}
|
||||
|
||||
/// Subscribe to all channels' timelines
|
||||
pub fn subscribe_all(
|
||||
&mut self,
|
||||
subs: &mut Subscriptions,
|
||||
timeline_cache: &mut TimelineCache,
|
||||
ctx: &mut AppContext,
|
||||
) {
|
||||
let txn = Transaction::new(ctx.ndb).unwrap();
|
||||
|
||||
for channel in &self.channels {
|
||||
if let Some(_result) = timeline_cache.open(
|
||||
subs,
|
||||
ctx.ndb,
|
||||
ctx.note_cache,
|
||||
&txn,
|
||||
ctx.pool,
|
||||
&channel.timeline_kind,
|
||||
) {
|
||||
// Process results if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unsubscribe from all channels
|
||||
pub fn unsubscribe_all(
|
||||
&mut self,
|
||||
timeline_cache: &mut TimelineCache,
|
||||
ndb: &mut nostrdb::Ndb,
|
||||
pool: &mut enostr::RelayPool,
|
||||
) {
|
||||
for channel in &self.channels {
|
||||
if let Err(err) = timeline_cache.pop(&channel.timeline_kind, ndb, pool) {
|
||||
error!("Failed to unsubscribe from channel timeline: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChannelList {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache mapping users to their channel lists
|
||||
pub struct ChannelsCache {
|
||||
account_to_channels: HashMap<Pubkey, ChannelList>,
|
||||
fallback_pubkey: Pubkey,
|
||||
}
|
||||
|
||||
impl ChannelsCache {
|
||||
pub fn new(
|
||||
mut account_to_channels: HashMap<Pubkey, ChannelList>,
|
||||
i18n: &mut Localization,
|
||||
) -> Self {
|
||||
let fallback_pubkey = FALLBACK_PUBKEY();
|
||||
account_to_channels
|
||||
.entry(fallback_pubkey)
|
||||
.or_insert_with(|| ChannelList::default_channels(i18n));
|
||||
|
||||
Self {
|
||||
account_to_channels,
|
||||
fallback_pubkey,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_channels_cache(i18n: &mut Localization) -> Self {
|
||||
let mut account_to_channels: HashMap<Pubkey, ChannelList> = Default::default();
|
||||
account_to_channels.insert(FALLBACK_PUBKEY(), ChannelList::default_channels(i18n));
|
||||
Self::new(account_to_channels, i18n)
|
||||
}
|
||||
|
||||
pub fn get_channels(&self, key: &Pubkey) -> &ChannelList {
|
||||
self.account_to_channels
|
||||
.get(key)
|
||||
.unwrap_or_else(|| self.fallback())
|
||||
}
|
||||
|
||||
pub fn get_channels_mut(&mut self, i18n: &mut Localization, key: &Pubkey) -> &mut ChannelList {
|
||||
self.account_to_channels
|
||||
.entry(*key)
|
||||
.or_insert_with(|| ChannelList::default_channels(i18n))
|
||||
}
|
||||
|
||||
pub fn active_channels(&self, accounts: ¬edeck::Accounts) -> &ChannelList {
|
||||
let account = accounts.get_selected_account();
|
||||
self.get_channels(&account.key.pubkey)
|
||||
}
|
||||
|
||||
pub fn active_channels_mut(
|
||||
&mut self,
|
||||
i18n: &mut Localization,
|
||||
accounts: ¬edeck::Accounts,
|
||||
) -> &mut ChannelList {
|
||||
let account = accounts.get_selected_account();
|
||||
self.get_channels_mut(i18n, &account.key.pubkey)
|
||||
}
|
||||
|
||||
pub fn selected_channel(&self, accounts: ¬edeck::Accounts) -> Option<&Channel> {
|
||||
self.active_channels(accounts).selected_channel()
|
||||
}
|
||||
|
||||
pub fn selected_channel_mut(
|
||||
&mut self,
|
||||
i18n: &mut Localization,
|
||||
accounts: ¬edeck::Accounts,
|
||||
) -> Option<&mut Channel> {
|
||||
self.active_channels_mut(i18n, accounts).selected_channel_mut()
|
||||
}
|
||||
|
||||
pub fn fallback(&self) -> &ChannelList {
|
||||
self.account_to_channels
|
||||
.get(&self.fallback_pubkey)
|
||||
.expect("fallback channel list not found")
|
||||
}
|
||||
|
||||
pub fn fallback_mut(&mut self) -> &mut ChannelList {
|
||||
self.account_to_channels
|
||||
.get_mut(&self.fallback_pubkey)
|
||||
.expect("fallback channel list not found")
|
||||
}
|
||||
|
||||
pub fn add_channel_for_account(
|
||||
&mut self,
|
||||
i18n: &mut Localization,
|
||||
pubkey: Pubkey,
|
||||
channel: Channel,
|
||||
) {
|
||||
let channel_name = channel.name.clone();
|
||||
let channels = self.get_channels_mut(i18n, &pubkey);
|
||||
channels.add_channel(channel);
|
||||
info!("Added channel '{}' for {:?}", channel_name, pubkey);
|
||||
}
|
||||
|
||||
pub fn remove(
|
||||
&mut self,
|
||||
i18n: &mut Localization,
|
||||
key: &Pubkey,
|
||||
timeline_cache: &mut TimelineCache,
|
||||
ndb: &mut nostrdb::Ndb,
|
||||
pool: &mut enostr::RelayPool,
|
||||
) {
|
||||
let Some(mut channels) = self.account_to_channels.remove(key) else {
|
||||
return;
|
||||
};
|
||||
info!("Removing channels for {:?}", key);
|
||||
|
||||
channels.unsubscribe_all(timeline_cache, ndb, pool);
|
||||
|
||||
if !self.account_to_channels.contains_key(&self.fallback_pubkey) {
|
||||
self.account_to_channels
|
||||
.insert(self.fallback_pubkey, ChannelList::default_channels(i18n));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fallback_pubkey(&self) -> &Pubkey {
|
||||
&self.fallback_pubkey
|
||||
}
|
||||
|
||||
pub fn get_mapping(&self) -> &HashMap<Pubkey, ChannelList> {
|
||||
&self.account_to_channels
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,12 @@ pub mod actionbar;
|
||||
pub mod app_creation;
|
||||
mod app_style;
|
||||
mod args;
|
||||
pub mod channels;
|
||||
pub mod column;
|
||||
mod deck_state;
|
||||
mod decks;
|
||||
mod draft;
|
||||
pub mod relay_config;
|
||||
mod key_parsing;
|
||||
pub mod login_manager;
|
||||
mod media_upload;
|
||||
|
||||
94
crates/notedeck_columns/src/relay_config.rs
Normal file
94
crates/notedeck_columns/src/relay_config.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use tracing::info;
|
||||
|
||||
/// Global relay configuration (not tied to user accounts)
|
||||
/// This determines which relays the app connects to for fetching events
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RelayConfig {
|
||||
/// List of relay URLs to monitor
|
||||
pub relays: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl RelayConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
relays: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a default configuration with some popular relays
|
||||
pub fn default_relays() -> Self {
|
||||
let mut relays = BTreeSet::new();
|
||||
|
||||
// Add some default public relays
|
||||
relays.insert("wss://relay.damus.io".to_string());
|
||||
relays.insert("wss://relay.nostr.band".to_string());
|
||||
relays.insert("wss://nos.lol".to_string());
|
||||
|
||||
Self { relays }
|
||||
}
|
||||
|
||||
pub fn add_relay(&mut self, url: String) -> bool {
|
||||
let inserted = self.relays.insert(url.clone());
|
||||
if inserted {
|
||||
info!("Added relay: {}", url);
|
||||
}
|
||||
inserted
|
||||
}
|
||||
|
||||
pub fn remove_relay(&mut self, url: &str) -> bool {
|
||||
let removed = self.relays.remove(url);
|
||||
if removed {
|
||||
info!("Removed relay: {}", url);
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
pub fn has_relay(&self, url: &str) -> bool {
|
||||
self.relays.contains(url)
|
||||
}
|
||||
|
||||
pub fn get_relays(&self) -> &BTreeSet<String> {
|
||||
&self.relays
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.relays.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.relays.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RelayConfig {
|
||||
fn default() -> Self {
|
||||
Self::default_relays()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_relay_config() {
|
||||
let mut config = RelayConfig::new();
|
||||
assert!(config.is_empty());
|
||||
|
||||
config.add_relay("wss://relay.example.com".to_string());
|
||||
assert_eq!(config.len(), 1);
|
||||
assert!(config.has_relay("wss://relay.example.com"));
|
||||
|
||||
config.remove_relay("wss://relay.example.com");
|
||||
assert!(config.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_relays() {
|
||||
let config = RelayConfig::default_relays();
|
||||
assert!(!config.is_empty());
|
||||
assert!(config.has_relay("wss://relay.damus.io"));
|
||||
}
|
||||
}
|
||||
170
crates/notedeck_columns/src/storage/channels.rs
Normal file
170
crates/notedeck_columns/src/storage/channels.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use enostr::Pubkey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, error};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::channels::{Channel, ChannelList, ChannelsCache};
|
||||
|
||||
use notedeck::{storage, DataPath, DataPathType, Directory, Localization};
|
||||
|
||||
pub static CHANNELS_CACHE_FILE: &str = "channels_cache.json";
|
||||
|
||||
pub fn load_channels_cache(path: &DataPath, i18n: &mut Localization) -> Option<ChannelsCache> {
|
||||
let data_path = path.path(DataPathType::Setting);
|
||||
|
||||
let channels_cache_str = match Directory::new(data_path).get_file(CHANNELS_CACHE_FILE.to_owned()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Could not read channels cache from file {}: {}",
|
||||
CHANNELS_CACHE_FILE, e
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let serializable_channels_cache =
|
||||
serde_json::from_str::<SerializableChannelsCache>(&channels_cache_str).ok()?;
|
||||
|
||||
Some(serializable_channels_cache.channels_cache(i18n))
|
||||
}
|
||||
|
||||
pub fn save_channels_cache(path: &DataPath, channels_cache: &ChannelsCache) {
|
||||
let serialized_channels_cache =
|
||||
match serde_json::to_string_pretty(&SerializableChannelsCache::to_serializable(channels_cache)) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!("Could not serialize channels cache: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let data_path = path.path(DataPathType::Setting);
|
||||
|
||||
if let Err(e) = storage::write_file(
|
||||
&data_path,
|
||||
CHANNELS_CACHE_FILE.to_string(),
|
||||
&serialized_channels_cache,
|
||||
) {
|
||||
error!(
|
||||
"Could not write channels cache to file {}: {}",
|
||||
CHANNELS_CACHE_FILE, e
|
||||
);
|
||||
} else {
|
||||
debug!("Successfully wrote channels cache to {}", CHANNELS_CACHE_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializableChannelsCache {
|
||||
#[serde(serialize_with = "serialize_map", deserialize_with = "deserialize_map")]
|
||||
channels_cache: HashMap<Pubkey, SerializableChannelList>,
|
||||
}
|
||||
|
||||
impl SerializableChannelsCache {
|
||||
fn to_serializable(channels_cache: &ChannelsCache) -> Self {
|
||||
SerializableChannelsCache {
|
||||
channels_cache: channels_cache
|
||||
.get_mapping()
|
||||
.iter()
|
||||
.map(|(k, v)| (*k, SerializableChannelList::from_channel_list(v)))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channels_cache(self, i18n: &mut Localization) -> ChannelsCache {
|
||||
let account_to_channels = self
|
||||
.channels_cache
|
||||
.into_iter()
|
||||
.map(|(pubkey, serializable_channels)| {
|
||||
(pubkey, serializable_channels.channel_list())
|
||||
})
|
||||
.collect();
|
||||
|
||||
ChannelsCache::new(account_to_channels, i18n)
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_map<S>(
|
||||
map: &HashMap<Pubkey, SerializableChannelList>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let stringified_map: HashMap<String, &SerializableChannelList> =
|
||||
map.iter().map(|(k, v)| (k.hex(), v)).collect();
|
||||
stringified_map.serialize(serializer)
|
||||
}
|
||||
|
||||
fn deserialize_map<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<HashMap<Pubkey, SerializableChannelList>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let stringified_map: HashMap<String, SerializableChannelList> =
|
||||
HashMap::deserialize(deserializer)?;
|
||||
|
||||
stringified_map
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
let key = Pubkey::from_hex(&k).map_err(serde::de::Error::custom)?;
|
||||
Ok((key, v))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializableChannelList {
|
||||
channels: Vec<SerializableChannel>,
|
||||
selected: usize,
|
||||
}
|
||||
|
||||
impl SerializableChannelList {
|
||||
pub fn from_channel_list(channel_list: &ChannelList) -> Self {
|
||||
Self {
|
||||
channels: channel_list
|
||||
.channels
|
||||
.iter()
|
||||
.map(SerializableChannel::from_channel)
|
||||
.collect(),
|
||||
selected: channel_list.selected,
|
||||
}
|
||||
}
|
||||
|
||||
fn channel_list(self) -> ChannelList {
|
||||
ChannelList {
|
||||
channels: self
|
||||
.channels
|
||||
.into_iter()
|
||||
.map(|c| c.channel())
|
||||
.collect(),
|
||||
selected: self.selected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializableChannel {
|
||||
id: String,
|
||||
name: String,
|
||||
hashtags: Vec<String>,
|
||||
}
|
||||
|
||||
impl SerializableChannel {
|
||||
pub fn from_channel(channel: &Channel) -> Self {
|
||||
Self {
|
||||
id: channel.id.to_string(),
|
||||
name: channel.name.clone(),
|
||||
hashtags: channel.hashtags.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channel(self) -> Channel {
|
||||
let id = Uuid::parse_str(&self.id).unwrap_or_else(|_| Uuid::new_v4());
|
||||
Channel::with_id(id, self.name, self.hashtags)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
mod channels;
|
||||
mod decks;
|
||||
mod relay_config;
|
||||
|
||||
pub use channels::{load_channels_cache, save_channels_cache, CHANNELS_CACHE_FILE};
|
||||
pub use decks::{load_decks_cache, save_decks_cache, DECKS_CACHE_FILE};
|
||||
pub use relay_config::{load_relay_config, save_relay_config, RELAY_CONFIG_FILE};
|
||||
|
||||
48
crates/notedeck_columns/src/storage/relay_config.rs
Normal file
48
crates/notedeck_columns/src/storage/relay_config.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use notedeck::{storage, DataPath, DataPathType, Directory};
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::relay_config::RelayConfig;
|
||||
|
||||
pub static RELAY_CONFIG_FILE: &str = "relay_config.json";
|
||||
|
||||
pub fn load_relay_config(path: &DataPath) -> Option<RelayConfig> {
|
||||
let data_path = path.path(DataPathType::Setting);
|
||||
|
||||
let relay_config_str = match Directory::new(data_path).get_file(RELAY_CONFIG_FILE.to_owned()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Could not read relay config from file {}: {}",
|
||||
RELAY_CONFIG_FILE, e
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
serde_json::from_str::<RelayConfig>(&relay_config_str).ok()
|
||||
}
|
||||
|
||||
pub fn save_relay_config(path: &DataPath, relay_config: &RelayConfig) {
|
||||
let serialized_relay_config = match serde_json::to_string_pretty(relay_config) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!("Could not serialize relay config: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let data_path = path.path(DataPathType::Setting);
|
||||
|
||||
if let Err(e) = storage::write_file(
|
||||
&data_path,
|
||||
RELAY_CONFIG_FILE.to_string(),
|
||||
&serialized_relay_config,
|
||||
) {
|
||||
error!(
|
||||
"Could not write relay config to file {}: {}",
|
||||
RELAY_CONFIG_FILE, e
|
||||
);
|
||||
} else {
|
||||
debug!("Successfully wrote relay config to {}", RELAY_CONFIG_FILE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user