mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-29 08:21:47 +00:00
[MIGRATION] Merge branch 'settings' into unstable
This commit is contained in:
commit
175a33cdb4
@ -239,7 +239,6 @@ impl Feed {
|
||||
// Filter further for the general feed
|
||||
let dismissed = GLOBALS.dismissed.read().await.clone();
|
||||
let now = Unixtime::now().unwrap();
|
||||
let one_month_ago = now - Duration::new(60 * 60 * 24 * 30, 0);
|
||||
|
||||
let current_feed_kind = self.current_feed_kind.read().to_owned();
|
||||
match current_feed_kind {
|
||||
@ -346,12 +345,14 @@ impl Feed {
|
||||
}
|
||||
}
|
||||
FeedKind::Person(person_pubkey) => {
|
||||
let since = now - Duration::from_secs(GLOBALS.settings.read().person_feed_chunk);
|
||||
|
||||
let events: Vec<(Unixtime, Id)> = GLOBALS
|
||||
.storage
|
||||
.find_events(
|
||||
&kinds, // feed kinds
|
||||
&[], // any person (due to delegation condition) // FIXME GINA
|
||||
Some(one_month_ago), // one year ago
|
||||
&kinds, // feed kinds
|
||||
&[], // any person (due to delegation condition) // FIXME
|
||||
Some(since),
|
||||
|e| {
|
||||
if dismissed.contains(&e.id) {
|
||||
return false;
|
||||
|
138
src/fetcher.rs
138
src/fetcher.rs
@ -26,11 +26,8 @@ pub enum FetchState {
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Fetcher {
|
||||
// we don't want new() to fail in lazy_static init, so we just mark it dead if there was an error
|
||||
// on creation
|
||||
dead: Option<String>,
|
||||
cache_dir: PathBuf,
|
||||
client: Client,
|
||||
cache_dir: RwLock<PathBuf>,
|
||||
client: RwLock<Option<Client>>,
|
||||
|
||||
// Here is where we store the current state of each URL being fetched
|
||||
urls: RwLock<HashMap<Url, FetchState>>,
|
||||
@ -44,49 +41,34 @@ pub struct Fetcher {
|
||||
|
||||
impl Fetcher {
|
||||
pub fn new() -> Fetcher {
|
||||
let connect_timeout = std::time::Duration::new(10, 0);
|
||||
let timeout = std::time::Duration::new(15, 0);
|
||||
let client = match Client::builder()
|
||||
.gzip(true)
|
||||
.brotli(true)
|
||||
.deflate(true)
|
||||
.connect_timeout(connect_timeout)
|
||||
.timeout(timeout)
|
||||
.build()
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return Fetcher {
|
||||
dead: Some(format!("{}", e)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut f: Fetcher = Fetcher {
|
||||
client,
|
||||
Fetcher {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Setup the cache directory
|
||||
let cache_dir = match Profile::current() {
|
||||
Ok(p) => p.cache_dir,
|
||||
Err(_) => {
|
||||
f.dead = Some("No Data Directory.".to_owned());
|
||||
return f;
|
||||
}
|
||||
};
|
||||
|
||||
f.cache_dir = cache_dir;
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start() {
|
||||
pub fn start() -> Result<(), Error> {
|
||||
// Setup the cache directory
|
||||
*GLOBALS.fetcher.cache_dir.write().unwrap() = Profile::current()?.cache_dir;
|
||||
|
||||
// Create client
|
||||
let connect_timeout =
|
||||
std::time::Duration::new(GLOBALS.settings.read().fetcher_connect_timeout_sec, 0);
|
||||
let timeout = std::time::Duration::new(GLOBALS.settings.read().fetcher_timeout_sec, 0);
|
||||
*GLOBALS.fetcher.client.write().unwrap() = Some(
|
||||
Client::builder()
|
||||
.gzip(true)
|
||||
.brotli(true)
|
||||
.deflate(true)
|
||||
.connect_timeout(connect_timeout)
|
||||
.timeout(timeout)
|
||||
.build()?,
|
||||
);
|
||||
|
||||
// Setup periodic queue management
|
||||
tokio::task::spawn(async {
|
||||
let fetcher_looptime_ms = GLOBALS.settings.read().fetcher_looptime_ms;
|
||||
tokio::task::spawn(async move {
|
||||
loop {
|
||||
// Every 1200 milliseconds...
|
||||
tokio::time::sleep(Duration::from_millis(1200)).await;
|
||||
tokio::time::sleep(Duration::from_millis(fetcher_looptime_ms)).await;
|
||||
|
||||
// Process the queue
|
||||
GLOBALS.fetcher.process_queue().await;
|
||||
@ -98,6 +80,8 @@ impl Fetcher {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn requests_queued(&self) -> usize {
|
||||
@ -121,9 +105,6 @@ impl Fetcher {
|
||||
}
|
||||
|
||||
pub async fn process_queue(&self) {
|
||||
if self.dead.is_some() {
|
||||
return;
|
||||
}
|
||||
if GLOBALS.settings.read().offline {
|
||||
return;
|
||||
}
|
||||
@ -150,7 +131,7 @@ impl Fetcher {
|
||||
}
|
||||
|
||||
let load = self.fetch_host_load(&host);
|
||||
if load >= 3 {
|
||||
if load >= GLOBALS.settings.read().fetcher_max_requests_per_host {
|
||||
continue; // We cannot overload any given host
|
||||
}
|
||||
|
||||
@ -183,11 +164,6 @@ impl Fetcher {
|
||||
// file system calls. This might be pushing the limits of what we should
|
||||
// be blocking on.
|
||||
|
||||
// Error if we are dead
|
||||
if let Some(reason) = &self.dead {
|
||||
return Err((format!("Fetcher is dead: {}", reason), file!(), line!()).into());
|
||||
}
|
||||
|
||||
// Do not fetch if offline
|
||||
if GLOBALS.settings.read().offline {
|
||||
return Ok(None);
|
||||
@ -271,14 +247,6 @@ impl Fetcher {
|
||||
}
|
||||
|
||||
async fn fetch(&self, url: Url) {
|
||||
// Error if we are dead
|
||||
if GLOBALS.fetcher.dead.is_some() {
|
||||
// mark as failed
|
||||
tracing::debug!("FETCH {url}: Failed: fetcher is dead");
|
||||
self.urls.write().unwrap().insert(url, FetchState::Failed);
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not fetch if offline
|
||||
if GLOBALS.settings.read().offline {
|
||||
tracing::debug!("FETCH {url}: Failed: offline mode");
|
||||
@ -312,7 +280,9 @@ impl Fetcher {
|
||||
.insert(url.clone(), FetchState::InFlight);
|
||||
|
||||
// Fetch the resource
|
||||
let client = GLOBALS.fetcher.client.clone(); // it is an Arc internally
|
||||
// it is an Arc internally
|
||||
let client = GLOBALS.fetcher.client.read().unwrap().clone().unwrap();
|
||||
|
||||
let mut req = client.get(&url.0);
|
||||
if let Some(ref etag) = etag {
|
||||
req = req.header("if-none-match", etag.to_owned());
|
||||
@ -384,6 +354,19 @@ impl Fetcher {
|
||||
|
||||
let maybe_response = req.send().await;
|
||||
|
||||
let low_exclusion = GLOBALS
|
||||
.settings
|
||||
.read()
|
||||
.fetcher_host_exclusion_on_low_error_secs;
|
||||
let med_exclusion = GLOBALS
|
||||
.settings
|
||||
.read()
|
||||
.fetcher_host_exclusion_on_med_error_secs;
|
||||
let high_exclusion = GLOBALS
|
||||
.settings
|
||||
.read()
|
||||
.fetcher_host_exclusion_on_high_error_secs;
|
||||
|
||||
// Deal with response errors
|
||||
let response = match maybe_response {
|
||||
Ok(r) => r,
|
||||
@ -391,11 +374,16 @@ impl Fetcher {
|
||||
if e.is_builder() {
|
||||
finish(FailOutcome::Fail, "builder error", Some(e.into()), 0);
|
||||
} else if e.is_timeout() {
|
||||
finish(FailOutcome::Requeue, "timeout", Some(e.into()), 30);
|
||||
finish(
|
||||
FailOutcome::Requeue,
|
||||
"timeout",
|
||||
Some(e.into()),
|
||||
low_exclusion,
|
||||
);
|
||||
} else if e.is_request() {
|
||||
finish(FailOutcome::Fail, "request error", Some(e.into()), 0);
|
||||
} else if e.is_connect() {
|
||||
finish(FailOutcome::Fail, "connect error", Some(e.into()), 15);
|
||||
finish(FailOutcome::Fail, "connect error", Some(e.into()), 0);
|
||||
} else if e.is_body() {
|
||||
finish(FailOutcome::Fail, "body error", Some(e.into()), 0);
|
||||
} else if e.is_decode() {
|
||||
@ -410,7 +398,12 @@ impl Fetcher {
|
||||
// Deal with status codes
|
||||
let status = response.status();
|
||||
if status.is_informational() {
|
||||
finish(FailOutcome::Requeue, "informational error", None, 30);
|
||||
finish(
|
||||
FailOutcome::Requeue,
|
||||
"informational error",
|
||||
None,
|
||||
low_exclusion,
|
||||
);
|
||||
return;
|
||||
} else if status.is_redirection() {
|
||||
if status == StatusCode::NOT_MODIFIED {
|
||||
@ -421,20 +414,23 @@ impl Fetcher {
|
||||
}
|
||||
return;
|
||||
} else if status.is_server_error() {
|
||||
finish(FailOutcome::Requeue, "server error", None, 300);
|
||||
// Give the server time to recover
|
||||
finish(FailOutcome::Requeue, "server error", None, high_exclusion);
|
||||
return;
|
||||
// give them 5 minutes, maybe the server will recover
|
||||
} else if status.is_success() {
|
||||
// fall through
|
||||
} else {
|
||||
match status {
|
||||
StatusCode::REQUEST_TIMEOUT => {
|
||||
finish(FailOutcome::Requeue, "request timeout", None, 30);
|
||||
// give 30 seconds and try again
|
||||
finish(FailOutcome::Requeue, "request timeout", None, low_exclusion);
|
||||
}
|
||||
StatusCode::TOO_MANY_REQUESTS => {
|
||||
finish(FailOutcome::Requeue, "too many requests", None, 30);
|
||||
// give 15 seconds and try again
|
||||
finish(
|
||||
FailOutcome::Requeue,
|
||||
"too many requests",
|
||||
None,
|
||||
med_exclusion,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
finish(FailOutcome::Fail, &format!("{}", status), None, 0);
|
||||
@ -501,7 +497,7 @@ impl Fetcher {
|
||||
hex::encode(result)
|
||||
};
|
||||
|
||||
let mut cache_file = self.cache_dir.clone();
|
||||
let mut cache_file = self.cache_dir.read().unwrap().clone();
|
||||
cache_file.push(hash);
|
||||
cache_file
|
||||
}
|
||||
|
21
src/media.rs
21
src/media.rs
@ -121,11 +121,10 @@ impl Media {
|
||||
return None; // can recover if the setting is switched
|
||||
}
|
||||
|
||||
match GLOBALS
|
||||
.fetcher
|
||||
.try_get(url, Duration::from_secs(60 * 60 * 24 * 3))
|
||||
{
|
||||
// cache expires in 3 days
|
||||
match GLOBALS.fetcher.try_get(
|
||||
url,
|
||||
Duration::from_secs(60 * 60 * GLOBALS.settings.read().media_becomes_stale_hours),
|
||||
) {
|
||||
Ok(None) => None,
|
||||
Ok(Some(bytes)) => {
|
||||
self.data_temp.insert(url.clone(), bytes);
|
||||
@ -158,8 +157,18 @@ pub(crate) fn load_image_bytes(
|
||||
image = crop_square(image);
|
||||
}
|
||||
if force_resize || image.width() > 16384 || image.height() > 16384 {
|
||||
// https://docs.rs/image/latest/image/imageops/enum.FilterType.html
|
||||
let algo = match &*GLOBALS.settings.read().image_resize_algorithm {
|
||||
"Nearest" => FilterType::Nearest,
|
||||
"Triangle" => FilterType::Triangle,
|
||||
"CatmullRom" => FilterType::CatmullRom,
|
||||
"Gaussian" => FilterType::Gaussian,
|
||||
"Lanczos3" => FilterType::Lanczos3,
|
||||
_ => FilterType::Triangle,
|
||||
};
|
||||
|
||||
// This preserves aspect ratio. The sizes represent bounds.
|
||||
image = image.resize(default_size, default_size, FilterType::Triangle);
|
||||
image = image.resize(default_size, default_size, algo);
|
||||
}
|
||||
let current_size = [image.width() as _, image.height() as _];
|
||||
let image_buffer = image.into_rgba8();
|
||||
|
@ -77,6 +77,9 @@ impl Minion {
|
||||
// minion will log when it connects
|
||||
tracing::trace!("{}: Minion handling started", &self.url);
|
||||
|
||||
let fetcher_timeout =
|
||||
std::time::Duration::new(GLOBALS.settings.read().fetcher_timeout_sec, 0);
|
||||
|
||||
// Connect to the relay
|
||||
let websocket_stream = {
|
||||
// Parse the URI
|
||||
@ -94,7 +97,7 @@ impl Minion {
|
||||
|
||||
// Fetch NIP-11 data
|
||||
let request_nip11_future = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::new(30, 0))
|
||||
.timeout(fetcher_timeout)
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.gzip(true)
|
||||
.brotli(true)
|
||||
@ -126,7 +129,12 @@ impl Minion {
|
||||
"{}: Unable to parse response as NIP-11 ({}): {}\n",
|
||||
&self.url,
|
||||
e,
|
||||
text.lines().take(10).collect::<Vec<_>>().join("\n")
|
||||
text.lines()
|
||||
.take(
|
||||
GLOBALS.settings.read().nip11_lines_to_output_on_error
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -182,13 +190,16 @@ impl Minion {
|
||||
// Cameri nostream relay limits to 0.5 a megabyte
|
||||
// Based on my current database of 7356 events, the longest was 11,121 bytes.
|
||||
// Cameri said people with >2k followers were losing data at 128kb cutoff.
|
||||
max_message_size: Some(1024 * 1024), // 1 MB
|
||||
max_frame_size: Some(1024 * 1024), // 1 MB
|
||||
accept_unmasked_frames: false, // default is false which is the standard
|
||||
max_message_size: Some(
|
||||
GLOBALS.settings.read().max_websocket_message_size_kb * 1024,
|
||||
),
|
||||
max_frame_size: Some(GLOBALS.settings.read().max_websocket_frame_size_kb * 1024),
|
||||
accept_unmasked_frames: GLOBALS.settings.read().websocket_accept_unmasked_frames,
|
||||
};
|
||||
|
||||
let connect_timeout = GLOBALS.settings.read().websocket_connect_timeout_sec;
|
||||
let (websocket_stream, response) = tokio::time::timeout(
|
||||
std::time::Duration::new(15, 0),
|
||||
std::time::Duration::new(connect_timeout, 0),
|
||||
tokio_tungstenite::connect_async_with_config(req, Some(config), false),
|
||||
)
|
||||
.await??;
|
||||
@ -250,7 +261,10 @@ impl Minion {
|
||||
async fn loop_handler(&mut self) -> Result<(), Error> {
|
||||
let ws_stream = self.stream.as_mut().unwrap();
|
||||
|
||||
let mut timer = tokio::time::interval(std::time::Duration::new(55, 0));
|
||||
let mut timer = tokio::time::interval(std::time::Duration::new(
|
||||
GLOBALS.settings.read().websocket_ping_frequency_sec,
|
||||
0,
|
||||
));
|
||||
timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
timer.tick().await; // use up the first immediate tick.
|
||||
|
||||
|
@ -108,7 +108,7 @@ impl Overlord {
|
||||
|
||||
pub async fn run_inner(&mut self) -> Result<(), Error> {
|
||||
// Start the fetcher
|
||||
crate::fetcher::Fetcher::start();
|
||||
crate::fetcher::Fetcher::start()?;
|
||||
|
||||
// Load signer from settings
|
||||
GLOBALS.signer.load_from_settings()?;
|
||||
@ -670,7 +670,8 @@ impl Overlord {
|
||||
.write("Pruning database, please be patient..".to_owned());
|
||||
|
||||
let now = Unixtime::now().unwrap();
|
||||
let then = now - Duration::new(60 * 60 * 24 * 180, 0); // 180 days
|
||||
let then = now
|
||||
- Duration::new(GLOBALS.settings.read().prune_period_days * 60 * 60 * 24, 0);
|
||||
let count = GLOBALS.storage.prune(then)?;
|
||||
|
||||
GLOBALS.status_queue.write().write(format!(
|
||||
|
@ -198,8 +198,11 @@ impl People {
|
||||
|
||||
task::spawn(async {
|
||||
loop {
|
||||
let fetch_metadata_looptime_ms =
|
||||
GLOBALS.settings.read().fetcher_metadata_looptime_ms;
|
||||
|
||||
// Every 3 seconds...
|
||||
tokio::time::sleep(Duration::from_millis(3000)).await;
|
||||
tokio::time::sleep(Duration::from_millis(fetch_metadata_looptime_ms)).await;
|
||||
|
||||
// We fetch needed metadata
|
||||
GLOBALS.people.maybe_fetch_metadata().await;
|
||||
@ -224,12 +227,11 @@ impl People {
|
||||
&self,
|
||||
among_these: &[PublicKey],
|
||||
) -> Vec<PublicKey> {
|
||||
let one_day_ago = Unixtime::now().unwrap().0 - (60 * 60 * 8);
|
||||
let stale = Unixtime::now().unwrap().0
|
||||
- 60 * 60 * GLOBALS.settings.read().relay_list_becomes_stale_hours as i64;
|
||||
|
||||
if let Ok(vec) = GLOBALS.storage.filter_people(|p| {
|
||||
p.followed
|
||||
&& p.relay_list_last_received < one_day_ago
|
||||
&& among_these.contains(&p.pubkey)
|
||||
p.followed && p.relay_list_last_received < stale && among_these.contains(&p.pubkey)
|
||||
}) {
|
||||
vec.iter().map(|p| p.pubkey).collect()
|
||||
} else {
|
||||
@ -270,9 +272,11 @@ impl People {
|
||||
let need = {
|
||||
// Metadata refresh interval
|
||||
let now = Unixtime::now().unwrap();
|
||||
let eight_hours = Duration::from_secs(60 * 60 * 8);
|
||||
let stale = Duration::from_secs(
|
||||
60 * 60 * GLOBALS.settings.read().metadata_becomes_stale_hours,
|
||||
);
|
||||
person.metadata_created_at.is_none()
|
||||
|| person.metadata_last_received < (now - eight_hours).0
|
||||
|| person.metadata_last_received < (now - stale).0
|
||||
};
|
||||
if !need {
|
||||
return;
|
||||
@ -394,9 +398,16 @@ impl People {
|
||||
} else if let Some(last) = person.nip05_last_checked {
|
||||
// FIXME make these settings
|
||||
let recheck_duration = if person.nip05_valid {
|
||||
Duration::from_secs(60 * 60 * 24 * 14)
|
||||
Duration::from_secs(
|
||||
60 * 60 * GLOBALS.settings.read().nip05_becomes_stale_if_valid_hours,
|
||||
)
|
||||
} else {
|
||||
Duration::from_secs(60 * 60 * 24)
|
||||
Duration::from_secs(
|
||||
60 * GLOBALS
|
||||
.settings
|
||||
.read()
|
||||
.nip05_becomes_stale_if_invalid_minutes,
|
||||
)
|
||||
};
|
||||
Unixtime::now().unwrap() - Unixtime(last as i64) > recheck_duration
|
||||
} else {
|
||||
@ -464,10 +475,10 @@ impl People {
|
||||
}
|
||||
};
|
||||
|
||||
match GLOBALS
|
||||
.fetcher
|
||||
.try_get(&url, Duration::from_secs(60 * 60 * 24 * 3))
|
||||
{
|
||||
match GLOBALS.fetcher.try_get(
|
||||
&url,
|
||||
Duration::from_secs(60 * 60 * GLOBALS.settings.read().avatar_becomes_stale_hours),
|
||||
) {
|
||||
// cache expires in 3 days
|
||||
Ok(None) => None,
|
||||
Ok(Some(bytes)) => {
|
||||
|
@ -46,7 +46,7 @@ pub async fn process_new_event(
|
||||
if let Some(ref relay_url) = seen_on {
|
||||
// Verify the event
|
||||
let mut maxtime = now;
|
||||
maxtime.0 += 60 * 15; // 15 minutes into the future
|
||||
maxtime.0 += GLOBALS.settings.read().future_allowance_secs as i64;
|
||||
if let Err(e) = event.verify(Some(maxtime)) {
|
||||
tracing::error!(
|
||||
"{}: VERIFY ERROR: {}, {}",
|
||||
|
180
src/settings.rs
180
src/settings.rs
@ -3,75 +3,173 @@ use nostr_types::{EventKind, PublicKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use speedy::{Readable, Writable};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Readable, Writable)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Readable, Writable, PartialEq)]
|
||||
pub struct Settings {
|
||||
pub feed_chunk: u64,
|
||||
pub replies_chunk: u64,
|
||||
pub overlap: u64,
|
||||
pub num_relays_per_person: u8,
|
||||
pub max_relays: u8,
|
||||
// ID settings
|
||||
pub public_key: Option<PublicKey>,
|
||||
pub max_fps: u32,
|
||||
pub recompute_feed_periodically: bool,
|
||||
pub feed_recompute_interval_ms: u32,
|
||||
pub pow: u8,
|
||||
pub log_n: u8,
|
||||
|
||||
// Network settings
|
||||
pub offline: bool,
|
||||
pub theme: Theme,
|
||||
pub set_client_tag: bool,
|
||||
pub set_user_agent: bool,
|
||||
pub override_dpi: Option<u32>,
|
||||
pub reactions: bool,
|
||||
pub reposts: bool,
|
||||
pub show_long_form: bool,
|
||||
pub show_mentions: bool,
|
||||
pub show_media: bool,
|
||||
pub load_avatars: bool,
|
||||
pub load_media: bool,
|
||||
pub check_nip05: bool,
|
||||
pub direct_messages: bool,
|
||||
pub automatically_fetch_metadata: bool,
|
||||
|
||||
// Relay settings
|
||||
pub num_relays_per_person: u8,
|
||||
pub max_relays: u8,
|
||||
|
||||
// Feed Settings
|
||||
pub feed_chunk: u64,
|
||||
pub replies_chunk: u64,
|
||||
pub person_feed_chunk: u64,
|
||||
pub overlap: u64,
|
||||
|
||||
// Event Selection
|
||||
pub reposts: bool,
|
||||
pub show_long_form: bool,
|
||||
pub show_mentions: bool,
|
||||
pub direct_messages: bool,
|
||||
pub future_allowance_secs: u64,
|
||||
|
||||
// Event Content Settings
|
||||
pub reactions: bool,
|
||||
pub enable_zap_receipts: bool,
|
||||
pub show_media: bool,
|
||||
|
||||
// Posting Settings
|
||||
pub pow: u8,
|
||||
pub set_client_tag: bool,
|
||||
pub set_user_agent: bool,
|
||||
pub delegatee_tag: String,
|
||||
|
||||
// UI settings
|
||||
pub max_fps: u32,
|
||||
pub recompute_feed_periodically: bool,
|
||||
pub feed_recompute_interval_ms: u32,
|
||||
pub theme: Theme,
|
||||
pub override_dpi: Option<u32>,
|
||||
pub highlight_unread_events: bool,
|
||||
pub posting_area_at_top: bool,
|
||||
pub enable_zap_receipts: bool,
|
||||
pub status_bar: bool,
|
||||
pub image_resize_algorithm: String,
|
||||
|
||||
// Staletime settings
|
||||
pub relay_list_becomes_stale_hours: u64,
|
||||
pub metadata_becomes_stale_hours: u64,
|
||||
pub nip05_becomes_stale_if_valid_hours: u64,
|
||||
pub nip05_becomes_stale_if_invalid_minutes: u64,
|
||||
pub avatar_becomes_stale_hours: u64,
|
||||
pub media_becomes_stale_hours: u64,
|
||||
|
||||
// Websocket settings
|
||||
pub max_websocket_message_size_kb: usize,
|
||||
pub max_websocket_frame_size_kb: usize,
|
||||
pub websocket_accept_unmasked_frames: bool,
|
||||
pub websocket_connect_timeout_sec: u64,
|
||||
pub websocket_ping_frequency_sec: u64,
|
||||
|
||||
// HTTP settings
|
||||
pub fetcher_metadata_looptime_ms: u64,
|
||||
pub fetcher_looptime_ms: u64,
|
||||
pub fetcher_connect_timeout_sec: u64,
|
||||
pub fetcher_timeout_sec: u64,
|
||||
pub fetcher_max_requests_per_host: usize,
|
||||
pub fetcher_host_exclusion_on_low_error_secs: u64,
|
||||
pub fetcher_host_exclusion_on_med_error_secs: u64,
|
||||
pub fetcher_host_exclusion_on_high_error_secs: u64,
|
||||
pub nip11_lines_to_output_on_error: usize,
|
||||
|
||||
// Database settings
|
||||
pub prune_period_days: u64,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
feed_chunk: 60 * 60 * 12, // 12 hours
|
||||
replies_chunk: 60 * 60 * 24 * 7, // 1 week
|
||||
overlap: 300, // 5 minutes
|
||||
// ID settings
|
||||
public_key: None,
|
||||
log_n: 18,
|
||||
|
||||
// Network settings
|
||||
offline: false,
|
||||
load_avatars: true,
|
||||
load_media: true,
|
||||
check_nip05: true,
|
||||
automatically_fetch_metadata: true,
|
||||
|
||||
// Relay settings
|
||||
num_relays_per_person: 2,
|
||||
max_relays: 50,
|
||||
public_key: None,
|
||||
|
||||
// Feed settings
|
||||
feed_chunk: 60 * 60 * 12, // 12 hours
|
||||
replies_chunk: 60 * 60 * 24 * 7, // 1 week
|
||||
person_feed_chunk: 60 * 60 * 24 * 30, // 1 month
|
||||
overlap: 300, // 5 minutes
|
||||
|
||||
// Event Selection
|
||||
reposts: true,
|
||||
show_long_form: false,
|
||||
show_mentions: true,
|
||||
direct_messages: true,
|
||||
future_allowance_secs: 60 * 15, // 15 minutes
|
||||
|
||||
// Event Content Settings
|
||||
reactions: true,
|
||||
enable_zap_receipts: true,
|
||||
show_media: true,
|
||||
|
||||
// Posting settings
|
||||
pow: 0,
|
||||
set_client_tag: false,
|
||||
set_user_agent: false,
|
||||
delegatee_tag: String::new(),
|
||||
|
||||
// UI settings
|
||||
max_fps: 12,
|
||||
recompute_feed_periodically: true,
|
||||
feed_recompute_interval_ms: 8000,
|
||||
pow: 0,
|
||||
offline: false,
|
||||
theme: Theme {
|
||||
variant: ThemeVariant::Default,
|
||||
dark_mode: false,
|
||||
follow_os_dark_mode: false,
|
||||
},
|
||||
set_client_tag: false,
|
||||
set_user_agent: false,
|
||||
override_dpi: None,
|
||||
reactions: true,
|
||||
reposts: true,
|
||||
show_long_form: false,
|
||||
show_mentions: true,
|
||||
show_media: true,
|
||||
load_avatars: true,
|
||||
load_media: true,
|
||||
check_nip05: true,
|
||||
direct_messages: true,
|
||||
automatically_fetch_metadata: true,
|
||||
delegatee_tag: String::new(),
|
||||
highlight_unread_events: true,
|
||||
posting_area_at_top: true,
|
||||
enable_zap_receipts: true,
|
||||
status_bar: false,
|
||||
image_resize_algorithm: "CatmullRom".to_owned(),
|
||||
|
||||
// Staletime settings
|
||||
relay_list_becomes_stale_hours: 8,
|
||||
metadata_becomes_stale_hours: 8,
|
||||
nip05_becomes_stale_if_valid_hours: 8,
|
||||
nip05_becomes_stale_if_invalid_minutes: 30, // 30 minutes
|
||||
avatar_becomes_stale_hours: 8,
|
||||
media_becomes_stale_hours: 8,
|
||||
|
||||
// Websocket settings
|
||||
max_websocket_message_size_kb: 1024, // 1 MB
|
||||
max_websocket_frame_size_kb: 1024, // 1 MB
|
||||
websocket_accept_unmasked_frames: false,
|
||||
websocket_connect_timeout_sec: 15,
|
||||
websocket_ping_frequency_sec: 55,
|
||||
|
||||
// HTTP settings
|
||||
fetcher_metadata_looptime_ms: 3000,
|
||||
fetcher_looptime_ms: 1800,
|
||||
fetcher_connect_timeout_sec: 15,
|
||||
fetcher_timeout_sec: 30,
|
||||
fetcher_max_requests_per_host: 3,
|
||||
fetcher_host_exclusion_on_low_error_secs: 30,
|
||||
fetcher_host_exclusion_on_med_error_secs: 60,
|
||||
fetcher_host_exclusion_on_high_error_secs: 600,
|
||||
nip11_lines_to_output_on_error: 10,
|
||||
|
||||
// Database settings
|
||||
prune_period_days: 30,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ use nostr_types::{
|
||||
use parking_lot::RwLock;
|
||||
use tokio::task;
|
||||
|
||||
const DEFAULT_LOG_N: u8 = 18;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Signer {
|
||||
public: RwLock<Option<PublicKey>>,
|
||||
@ -70,7 +68,7 @@ impl Signer {
|
||||
}
|
||||
|
||||
pub fn set_private_key(&self, pk: PrivateKey, pass: &str) -> Result<(), Error> {
|
||||
*self.encrypted.write() = Some(pk.export_encrypted(pass, DEFAULT_LOG_N)?);
|
||||
*self.encrypted.write() = Some(pk.export_encrypted(pass, GLOBALS.settings.read().log_n)?);
|
||||
*self.public.write() = Some(pk.public_key());
|
||||
*self.private.write() = Some(pk);
|
||||
Ok(())
|
||||
@ -88,7 +86,8 @@ impl Signer {
|
||||
|
||||
// If older version, re-encrypt with new version at default 2^18 rounds
|
||||
if epk.version()? < 2 {
|
||||
*self.encrypted.write() = Some(private.export_encrypted(pass, DEFAULT_LOG_N)?);
|
||||
*self.encrypted.write() =
|
||||
Some(private.export_encrypted(pass, GLOBALS.settings.read().log_n)?);
|
||||
// and eventually save
|
||||
task::spawn(async move {
|
||||
if let Err(e) = GLOBALS.signer.save_through_settings().await {
|
||||
@ -130,7 +129,7 @@ impl Signer {
|
||||
Some(epk) => {
|
||||
// Test password
|
||||
let pk = epk.decrypt(old)?;
|
||||
let epk = pk.export_encrypted(new, DEFAULT_LOG_N)?;
|
||||
let epk = pk.export_encrypted(new, GLOBALS.settings.read().log_n)?;
|
||||
*self.encrypted.write() = Some(epk);
|
||||
task::spawn(async move {
|
||||
if let Err(e) = GLOBALS.signer.save_through_settings().await {
|
||||
@ -149,7 +148,7 @@ impl Signer {
|
||||
|
||||
pub fn generate_private_key(&self, pass: &str) -> Result<(), Error> {
|
||||
let pk = PrivateKey::generate();
|
||||
*self.encrypted.write() = Some(pk.export_encrypted(pass, DEFAULT_LOG_N)?);
|
||||
*self.encrypted.write() = Some(pk.export_encrypted(pass, GLOBALS.settings.read().log_n)?);
|
||||
*self.public.write() = Some(pk.public_key());
|
||||
*self.private.write() = Some(pk);
|
||||
Ok(())
|
||||
@ -201,7 +200,7 @@ impl Signer {
|
||||
|
||||
// We have to regenerate encrypted private key because it may have fallen from
|
||||
// medium to weak security. And then we need to save that
|
||||
let epk = pk.export_encrypted(pass, DEFAULT_LOG_N)?;
|
||||
let epk = pk.export_encrypted(pass, GLOBALS.settings.read().log_n)?;
|
||||
*self.encrypted.write() = Some(epk);
|
||||
*self.private.write() = Some(pk);
|
||||
task::spawn(async move {
|
||||
@ -226,7 +225,7 @@ impl Signer {
|
||||
|
||||
// We have to regenerate encrypted private key because it may have fallen from
|
||||
// medium to weak security. And then we need to save that
|
||||
let epk = pk.export_encrypted(pass, DEFAULT_LOG_N)?;
|
||||
let epk = pk.export_encrypted(pass, GLOBALS.settings.read().log_n)?;
|
||||
*self.encrypted.write() = Some(epk);
|
||||
*self.private.write() = Some(pk);
|
||||
task::spawn(async move {
|
||||
|
@ -4,11 +4,13 @@ use lmdb::{Cursor, RwTransaction, Transaction};
|
||||
use nostr_types::Event;
|
||||
use speedy::Readable;
|
||||
|
||||
mod settings;
|
||||
|
||||
impl Storage {
|
||||
const MIGRATION_LEVEL: u32 = 1;
|
||||
const MAX_MIGRATION_LEVEL: u32 = 2;
|
||||
|
||||
pub(super) fn migrate(&self, mut level: u32) -> Result<(), Error> {
|
||||
if level > Self::MIGRATION_LEVEL {
|
||||
if level > Self::MAX_MIGRATION_LEVEL {
|
||||
return Err(ErrorKind::General(format!(
|
||||
"Migration level {} unknown: This client is older than your data.",
|
||||
level
|
||||
@ -17,47 +19,48 @@ impl Storage {
|
||||
}
|
||||
|
||||
let mut txn = self.env.begin_rw_txn()?;
|
||||
|
||||
while level < Self::MIGRATION_LEVEL {
|
||||
while level < Self::MAX_MIGRATION_LEVEL {
|
||||
self.migrate_inner(level, &mut txn)?;
|
||||
level += 1;
|
||||
tracing::info!("LMDB Migration to level {}...", level);
|
||||
self.migrate_inner(level, Some(&mut txn))?;
|
||||
self.write_migration_level(level, Some(&mut txn))?;
|
||||
}
|
||||
|
||||
txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_inner<'a>(
|
||||
&'a self,
|
||||
level: u32,
|
||||
rw_txn: Option<&mut RwTransaction<'a>>,
|
||||
) -> Result<(), Error> {
|
||||
fn migrate_inner<'a>(&'a self, level: u32, txn: &mut RwTransaction<'a>) -> Result<(), Error> {
|
||||
let prefix = format!("LMDB Migration {} -> {}", level, level + 1);
|
||||
match level {
|
||||
0 => Ok(()),
|
||||
1 => self.compute_relationships(rw_txn),
|
||||
n => panic!("Unknown migration level {}", n),
|
||||
}
|
||||
0 => {
|
||||
let total = self.get_event_stats()?.entries();
|
||||
tracing::info!(
|
||||
"{prefix}: Computing and storing event relationships for {total} events..."
|
||||
);
|
||||
self.compute_relationships(total, Some(txn))?;
|
||||
}
|
||||
1 => {
|
||||
tracing::info!("{prefix}: Updating Settings...");
|
||||
self.try_migrate_settings1_settings2(Some(txn))?;
|
||||
}
|
||||
_ => panic!("Unreachable migration level"),
|
||||
};
|
||||
|
||||
tracing::info!("done.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Load and process every event in order to generate the relationships data
|
||||
fn compute_relationships<'a>(
|
||||
&'a self,
|
||||
total: usize,
|
||||
rw_txn: Option<&mut RwTransaction<'a>>,
|
||||
) -> Result<(), Error> {
|
||||
self.disable_sync()?;
|
||||
|
||||
let f = |txn: &mut RwTransaction<'a>| -> Result<(), Error> {
|
||||
// track progress
|
||||
let total = self.get_event_stats()?.entries();
|
||||
|
||||
tracing::info!(
|
||||
"LMDB Migration 1: Computing and storing event relationships for {} events...",
|
||||
total
|
||||
);
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
let event_txn = self.env.begin_ro_txn()?;
|
||||
|
52
src/storage/migrations/settings/mod.rs
Normal file
52
src/storage/migrations/settings/mod.rs
Normal file
@ -0,0 +1,52 @@
|
||||
// In order for this migration to work in the distant future after all kinds of
|
||||
// other code has changed, it has to have it's own version of what Settings used
|
||||
// to look like.
|
||||
|
||||
mod settings1;
|
||||
use settings1::Settings1;
|
||||
|
||||
mod settings2;
|
||||
use settings2::Settings2;
|
||||
|
||||
mod theme1;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::storage::Storage;
|
||||
use lmdb::{RwTransaction, Transaction, WriteFlags};
|
||||
use speedy::{Readable, Writable};
|
||||
|
||||
impl Storage {
|
||||
pub(in crate::storage) fn try_migrate_settings1_settings2<'a>(
|
||||
&'a self,
|
||||
rw_txn: Option<&mut RwTransaction<'a>>,
|
||||
) -> Result<(), Error> {
|
||||
let f = |txn: &mut RwTransaction<'a>| -> Result<(), Error> {
|
||||
// If something is under the old "settings" key
|
||||
if let Ok(bytes) = txn.get(self.general, b"settings") {
|
||||
let settings1 = Settings1::read_from_buffer(bytes)?;
|
||||
|
||||
// Convert it to the new Settings2 structure
|
||||
let settings2: Settings2 = settings1.into();
|
||||
let bytes = settings2.write_to_vec()?;
|
||||
|
||||
// And store it under the new "settings2" key
|
||||
txn.put(self.general, b"settings2", &bytes, WriteFlags::empty())?;
|
||||
|
||||
// Then delete the old "settings" key
|
||||
txn.del(self.general, b"settings", None)?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
match rw_txn {
|
||||
Some(txn) => f(txn)?,
|
||||
None => {
|
||||
let mut txn = self.env.begin_rw_txn()?;
|
||||
f(&mut txn)?;
|
||||
txn.commit()?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
77
src/storage/migrations/settings/settings1.rs
Normal file
77
src/storage/migrations/settings/settings1.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use super::theme1::{Theme1, ThemeVariant1};
|
||||
use nostr_types::PublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use speedy::{Readable, Writable};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Readable, Writable)]
|
||||
pub struct Settings1 {
|
||||
pub feed_chunk: u64,
|
||||
pub replies_chunk: u64,
|
||||
pub overlap: u64,
|
||||
pub num_relays_per_person: u8,
|
||||
pub max_relays: u8,
|
||||
pub public_key: Option<PublicKey>,
|
||||
pub max_fps: u32,
|
||||
pub recompute_feed_periodically: bool,
|
||||
pub feed_recompute_interval_ms: u32,
|
||||
pub pow: u8,
|
||||
pub offline: bool,
|
||||
pub theme: Theme1,
|
||||
pub set_client_tag: bool,
|
||||
pub set_user_agent: bool,
|
||||
pub override_dpi: Option<u32>,
|
||||
pub reactions: bool,
|
||||
pub reposts: bool,
|
||||
pub show_long_form: bool,
|
||||
pub show_mentions: bool,
|
||||
pub show_media: bool,
|
||||
pub load_avatars: bool,
|
||||
pub load_media: bool,
|
||||
pub check_nip05: bool,
|
||||
pub direct_messages: bool,
|
||||
pub automatically_fetch_metadata: bool,
|
||||
pub delegatee_tag: String,
|
||||
pub highlight_unread_events: bool,
|
||||
pub posting_area_at_top: bool,
|
||||
pub enable_zap_receipts: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings1 {
|
||||
fn default() -> Settings1 {
|
||||
Settings1 {
|
||||
feed_chunk: 60 * 60 * 12, // 12 hours
|
||||
replies_chunk: 60 * 60 * 24 * 7, // 1 week
|
||||
overlap: 300, // 5 minutes
|
||||
num_relays_per_person: 2,
|
||||
max_relays: 50,
|
||||
public_key: None,
|
||||
max_fps: 12,
|
||||
recompute_feed_periodically: true,
|
||||
feed_recompute_interval_ms: 8000,
|
||||
pow: 0,
|
||||
offline: false,
|
||||
theme: Theme1 {
|
||||
variant: ThemeVariant1::Default,
|
||||
dark_mode: false,
|
||||
follow_os_dark_mode: false,
|
||||
},
|
||||
set_client_tag: false,
|
||||
set_user_agent: false,
|
||||
override_dpi: None,
|
||||
reactions: true,
|
||||
reposts: true,
|
||||
show_long_form: false,
|
||||
show_mentions: true,
|
||||
show_media: true,
|
||||
load_avatars: true,
|
||||
load_media: true,
|
||||
check_nip05: true,
|
||||
direct_messages: true,
|
||||
automatically_fetch_metadata: true,
|
||||
delegatee_tag: String::new(),
|
||||
highlight_unread_events: true,
|
||||
posting_area_at_top: true,
|
||||
enable_zap_receipts: true,
|
||||
}
|
||||
}
|
||||
}
|
229
src/storage/migrations/settings/settings2.rs
Normal file
229
src/storage/migrations/settings/settings2.rs
Normal file
@ -0,0 +1,229 @@
|
||||
use super::settings1::Settings1;
|
||||
use super::theme1::{Theme1, ThemeVariant1};
|
||||
use nostr_types::PublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use speedy::{Readable, Writable};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Readable, Writable)]
|
||||
pub struct Settings2 {
|
||||
// ID settings
|
||||
pub public_key: Option<PublicKey>,
|
||||
pub log_n: u8,
|
||||
|
||||
// Network settings
|
||||
pub offline: bool,
|
||||
pub load_avatars: bool,
|
||||
pub load_media: bool,
|
||||
pub check_nip05: bool,
|
||||
pub automatically_fetch_metadata: bool,
|
||||
|
||||
// Relay settings
|
||||
pub num_relays_per_person: u8,
|
||||
pub max_relays: u8,
|
||||
|
||||
// Feed Settings
|
||||
pub feed_chunk: u64,
|
||||
pub replies_chunk: u64,
|
||||
pub person_feed_chunk: u64,
|
||||
pub overlap: u64,
|
||||
|
||||
// Event Selection
|
||||
pub reposts: bool,
|
||||
pub show_long_form: bool,
|
||||
pub show_mentions: bool,
|
||||
pub direct_messages: bool,
|
||||
pub future_allowance_secs: u64,
|
||||
|
||||
// Event Content Settings
|
||||
pub reactions: bool,
|
||||
pub enable_zap_receipts: bool,
|
||||
pub show_media: bool,
|
||||
|
||||
// Posting Settings
|
||||
pub pow: u8,
|
||||
pub set_client_tag: bool,
|
||||
pub set_user_agent: bool,
|
||||
pub delegatee_tag: String,
|
||||
|
||||
// UI settings
|
||||
pub max_fps: u32,
|
||||
pub recompute_feed_periodically: bool,
|
||||
pub feed_recompute_interval_ms: u32,
|
||||
pub theme: Theme1,
|
||||
pub override_dpi: Option<u32>,
|
||||
pub highlight_unread_events: bool,
|
||||
pub posting_area_at_top: bool,
|
||||
pub status_bar: bool,
|
||||
pub image_resize_algorithm: String,
|
||||
|
||||
// Staletime settings
|
||||
pub relay_list_becomes_stale_hours: u64,
|
||||
pub metadata_becomes_stale_hours: u64,
|
||||
pub nip05_becomes_stale_if_valid_hours: u64,
|
||||
pub nip05_becomes_stale_if_invalid_minutes: u64,
|
||||
pub avatar_becomes_stale_hours: u64,
|
||||
pub media_becomes_stale_hours: u64,
|
||||
|
||||
// Websocket settings
|
||||
pub max_websocket_message_size_kb: usize,
|
||||
pub max_websocket_frame_size_kb: usize,
|
||||
pub websocket_accept_unmasked_frames: bool,
|
||||
pub websocket_connect_timeout_sec: u64,
|
||||
pub websocket_ping_frequency_sec: u64,
|
||||
|
||||
// HTTP settings
|
||||
pub fetcher_metadata_looptime_ms: u64,
|
||||
pub fetcher_looptime_ms: u64,
|
||||
pub fetcher_connect_timeout_sec: u64,
|
||||
pub fetcher_timeout_sec: u64,
|
||||
pub fetcher_max_requests_per_host: usize,
|
||||
pub fetcher_host_exclusion_on_low_error_secs: u64,
|
||||
pub fetcher_host_exclusion_on_med_error_secs: u64,
|
||||
pub fetcher_host_exclusion_on_high_error_secs: u64,
|
||||
pub nip11_lines_to_output_on_error: usize,
|
||||
|
||||
// Database settings
|
||||
pub prune_period_days: u64,
|
||||
}
|
||||
|
||||
impl Default for Settings2 {
|
||||
fn default() -> Settings2 {
|
||||
Settings2 {
|
||||
// ID settings
|
||||
public_key: None,
|
||||
log_n: 18,
|
||||
|
||||
// Network settings
|
||||
offline: false,
|
||||
load_avatars: true,
|
||||
load_media: true,
|
||||
check_nip05: true,
|
||||
automatically_fetch_metadata: true,
|
||||
|
||||
// Relay settings
|
||||
num_relays_per_person: 2,
|
||||
max_relays: 50,
|
||||
|
||||
// Feed settings
|
||||
feed_chunk: 60 * 60 * 12, // 12 hours
|
||||
replies_chunk: 60 * 60 * 24 * 7, // 1 week
|
||||
person_feed_chunk: 60 * 60 * 24 * 30, // 1 month
|
||||
overlap: 300, // 5 minutes
|
||||
|
||||
// Event Selection
|
||||
reposts: true,
|
||||
show_long_form: false,
|
||||
show_mentions: true,
|
||||
direct_messages: true,
|
||||
future_allowance_secs: 60 * 15, // 15 minutes
|
||||
|
||||
// Event Content Settings
|
||||
reactions: true,
|
||||
enable_zap_receipts: true,
|
||||
show_media: true,
|
||||
|
||||
// Posting settings
|
||||
pow: 0,
|
||||
set_client_tag: false,
|
||||
set_user_agent: false,
|
||||
delegatee_tag: String::new(),
|
||||
|
||||
// UI settings
|
||||
max_fps: 12,
|
||||
recompute_feed_periodically: true,
|
||||
feed_recompute_interval_ms: 8000,
|
||||
theme: Theme1 {
|
||||
variant: ThemeVariant1::Default,
|
||||
dark_mode: false,
|
||||
follow_os_dark_mode: false,
|
||||
},
|
||||
override_dpi: None,
|
||||
highlight_unread_events: true,
|
||||
posting_area_at_top: true,
|
||||
status_bar: false,
|
||||
image_resize_algorithm: "CatmullRom".to_owned(),
|
||||
|
||||
// Staletime settings
|
||||
relay_list_becomes_stale_hours: 8,
|
||||
metadata_becomes_stale_hours: 8,
|
||||
nip05_becomes_stale_if_valid_hours: 8,
|
||||
nip05_becomes_stale_if_invalid_minutes: 30, // 30 minutes
|
||||
avatar_becomes_stale_hours: 8,
|
||||
media_becomes_stale_hours: 8,
|
||||
|
||||
// Websocket settings
|
||||
max_websocket_message_size_kb: 1024, // 1 MB
|
||||
max_websocket_frame_size_kb: 1024, // 1 MB
|
||||
websocket_accept_unmasked_frames: false,
|
||||
websocket_connect_timeout_sec: 15,
|
||||
websocket_ping_frequency_sec: 55,
|
||||
|
||||
// HTTP settings
|
||||
fetcher_metadata_looptime_ms: 3000,
|
||||
fetcher_looptime_ms: 1800,
|
||||
fetcher_connect_timeout_sec: 15,
|
||||
fetcher_timeout_sec: 30,
|
||||
fetcher_max_requests_per_host: 3,
|
||||
fetcher_host_exclusion_on_low_error_secs: 30,
|
||||
fetcher_host_exclusion_on_med_error_secs: 60,
|
||||
fetcher_host_exclusion_on_high_error_secs: 600,
|
||||
nip11_lines_to_output_on_error: 10,
|
||||
|
||||
// Database settings
|
||||
prune_period_days: 30,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Settings1> for Settings2 {
|
||||
fn from(old: Settings1) -> Settings2 {
|
||||
Settings2 {
|
||||
// ID settings
|
||||
public_key: old.public_key,
|
||||
|
||||
// Network settings
|
||||
offline: old.offline,
|
||||
load_avatars: old.load_avatars,
|
||||
load_media: old.load_media,
|
||||
check_nip05: old.check_nip05,
|
||||
automatically_fetch_metadata: old.automatically_fetch_metadata,
|
||||
|
||||
// Relay settings
|
||||
num_relays_per_person: old.num_relays_per_person,
|
||||
max_relays: old.max_relays,
|
||||
|
||||
// Feed settings
|
||||
feed_chunk: old.feed_chunk,
|
||||
replies_chunk: old.replies_chunk,
|
||||
overlap: old.overlap,
|
||||
|
||||
// Event Selection
|
||||
reposts: old.reposts,
|
||||
show_long_form: old.show_long_form,
|
||||
show_mentions: old.show_mentions,
|
||||
direct_messages: old.direct_messages,
|
||||
|
||||
// Event Content Settings
|
||||
reactions: old.reactions,
|
||||
enable_zap_receipts: old.enable_zap_receipts,
|
||||
show_media: old.show_media,
|
||||
|
||||
// Posting settings
|
||||
pow: old.pow,
|
||||
set_client_tag: old.set_client_tag,
|
||||
set_user_agent: old.set_user_agent,
|
||||
delegatee_tag: old.delegatee_tag,
|
||||
|
||||
// UI settings
|
||||
max_fps: old.max_fps,
|
||||
recompute_feed_periodically: old.recompute_feed_periodically,
|
||||
feed_recompute_interval_ms: old.feed_recompute_interval_ms,
|
||||
theme: old.theme,
|
||||
override_dpi: old.override_dpi,
|
||||
highlight_unread_events: old.highlight_unread_events,
|
||||
posting_area_at_top: old.posting_area_at_top,
|
||||
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
17
src/storage/migrations/settings/theme1.rs
Normal file
17
src/storage/migrations/settings/theme1.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use speedy::{Readable, Writable};
|
||||
|
||||
// note: if we store anything inside the variants, we can't use macro_rules.
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Readable, Writable)]
|
||||
pub enum ThemeVariant1 {
|
||||
Classic,
|
||||
Default,
|
||||
Roundy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Readable, Writable)]
|
||||
pub struct Theme1 {
|
||||
pub variant: ThemeVariant1,
|
||||
pub dark_mode: bool,
|
||||
pub follow_os_dark_mode: bool,
|
||||
}
|
@ -113,6 +113,8 @@ impl Storage {
|
||||
// Some filesystem that doesn't handle sparse files may allocate all
|
||||
// of this, so we don't go too crazy big.
|
||||
|
||||
// NOTE: this cannot be a setting because settings are only available
|
||||
// after the database has been launched.
|
||||
builder.set_map_size(1048576 * 1024 * 24); // 24 GB
|
||||
|
||||
let env = builder.open(&Profile::current()?.lmdb_dir)?;
|
||||
@ -498,7 +500,7 @@ impl Storage {
|
||||
let bytes = settings.write_to_vec()?;
|
||||
|
||||
let f = |txn: &mut RwTransaction<'a>| -> Result<(), Error> {
|
||||
txn.put(self.general, b"settings", &bytes, WriteFlags::empty())?;
|
||||
txn.put(self.general, b"settings2", &bytes, WriteFlags::empty())?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
@ -516,7 +518,7 @@ impl Storage {
|
||||
|
||||
pub fn read_settings(&self) -> Result<Option<Settings>, Error> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
match txn.get(self.general, b"settings") {
|
||||
match txn.get(self.general, b"settings2") {
|
||||
Ok(bytes) => Ok(Some(Settings::read_from_buffer(bytes)?)),
|
||||
Err(lmdb::Error::NotFound) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
|
@ -134,6 +134,16 @@ struct SubMenuState {
|
||||
submenu_states: HashMap<SubMenu, bool>,
|
||||
}
|
||||
|
||||
#[derive(Eq, Hash, PartialEq)]
|
||||
enum SettingsTab {
|
||||
Content,
|
||||
Database,
|
||||
Id,
|
||||
Network,
|
||||
Posting,
|
||||
Ui,
|
||||
}
|
||||
|
||||
impl SubMenuState {
|
||||
fn new() -> Self {
|
||||
let mut submenu_states: HashMap<SubMenu, bool> = HashMap::new();
|
||||
@ -168,6 +178,7 @@ struct GossipUi {
|
||||
next_frame: Instant,
|
||||
override_dpi: bool,
|
||||
override_dpi_value: u32,
|
||||
original_dpi_value: u32,
|
||||
current_scroll_offset: f32,
|
||||
future_scroll_offset: f32,
|
||||
|
||||
@ -195,6 +206,7 @@ struct GossipUi {
|
||||
inbox_include_indirect: bool,
|
||||
submenu_ids: HashMap<SubMenu, egui::Id>,
|
||||
submenu_state: SubMenuState,
|
||||
settings_tab: SettingsTab,
|
||||
|
||||
// General Data
|
||||
about: About,
|
||||
@ -392,6 +404,7 @@ impl GossipUi {
|
||||
next_frame: Instant::now(),
|
||||
override_dpi,
|
||||
override_dpi_value,
|
||||
original_dpi_value: override_dpi_value,
|
||||
current_scroll_offset: 0.0,
|
||||
future_scroll_offset: 0.0,
|
||||
qr_codes: HashMap::new(),
|
||||
@ -414,6 +427,7 @@ impl GossipUi {
|
||||
.unwrap_or(false),
|
||||
submenu_ids,
|
||||
submenu_state: SubMenuState::new(),
|
||||
settings_tab: SettingsTab::Id,
|
||||
about: crate::about::about(),
|
||||
icon: icon_texture_handle,
|
||||
placeholder_avatar: placeholder_avatar_texture_handle,
|
||||
@ -576,6 +590,55 @@ impl eframe::App for GossipUi {
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.status_bar {
|
||||
egui::TopBottomPanel::top("stats-bar")
|
||||
.frame(
|
||||
egui::Frame::side_top_panel(&self.settings.theme.get_style()).inner_margin(
|
||||
egui::Margin {
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
},
|
||||
),
|
||||
)
|
||||
.show(
|
||||
ctx,
|
||||
|ui| {
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::BOTTOM), |ui| {
|
||||
let in_flight = GLOBALS.fetcher.requests_in_flight();
|
||||
let queued = GLOBALS.fetcher.requests_queued();
|
||||
let events = GLOBALS
|
||||
.storage
|
||||
.get_event_stats()
|
||||
.map(|s| s.entries())
|
||||
.unwrap_or(0);
|
||||
let relays = GLOBALS.connected_relays.len();
|
||||
let processed = GLOBALS.events_processed.load(Ordering::Relaxed);
|
||||
let subs = GLOBALS.open_subscriptions.load(Ordering::Relaxed);
|
||||
let stats_message = format!(
|
||||
"EVENTS PROCESSED={} STORED={} RELAYS CONNS={} SUBS={} HTTP: {} / {}",
|
||||
processed,
|
||||
events,
|
||||
relays,
|
||||
subs,
|
||||
in_flight,
|
||||
in_flight + queued
|
||||
);
|
||||
let stats_message = RichText::new(stats_message)
|
||||
.color(self.settings.theme.notice_marker_text_color());
|
||||
ui.add(Label::new(stats_message))
|
||||
.on_hover_text(
|
||||
"events processed: number of events relays have sent to us, including duplicates.\n\
|
||||
events stored: number of unique events in storage\n\
|
||||
relay conns: number of relays currently connected\n\
|
||||
relay subs: number of subscriptions that have not come to EOSE yet\n\
|
||||
http: number of fetches in flight / number of requests queued");
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
egui::SidePanel::left("main-naviation-panel")
|
||||
.show_separator_line(false)
|
||||
.frame(
|
||||
@ -825,41 +888,6 @@ impl eframe::App for GossipUi {
|
||||
ui.add_space(7.0);
|
||||
feed::post::posting_area(self, ctx, frame, ui);
|
||||
}
|
||||
/*
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(5.0);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::BOTTOM), |ui| {
|
||||
let in_flight = GLOBALS.fetcher.requests_in_flight();
|
||||
let queued = GLOBALS.fetcher.requests_queued();
|
||||
let events = GLOBALS
|
||||
.storage
|
||||
.get_event_stats()
|
||||
.map(|s| s.entries())
|
||||
.unwrap_or(0);
|
||||
let relays = GLOBALS.connected_relays.len();
|
||||
let processed = GLOBALS.events_processed.load(Ordering::Relaxed);
|
||||
let subs = GLOBALS.open_subscriptions.load(Ordering::Relaxed);
|
||||
let stats_message = format!(
|
||||
"EVENTS PROCESSED={} STORED={} RELAYS CONNS={} SUBS={} HTTP: {} / {}",
|
||||
processed,
|
||||
events,
|
||||
relays,
|
||||
subs,
|
||||
in_flight,
|
||||
in_flight + queued
|
||||
);
|
||||
let stats_message = RichText::new(stats_message)
|
||||
.color(self.settings.theme.notice_marker_text_color());
|
||||
ui.add(Label::new(stats_message))
|
||||
.on_hover_text(
|
||||
"events processed: number of events relays have sent to us, including duplicates.\n\
|
||||
events stored: number of unique events in storage\n\
|
||||
relay conns: number of relays currently connected\n\
|
||||
relay subs: number of subscriptions that have not come to EOSE yet\n\
|
||||
http: number of fetches in flight / number of requests queued");
|
||||
});
|
||||
});
|
||||
*/
|
||||
});
|
||||
|
||||
// Prepare local zap data once per frame for easier compute at render time
|
||||
|
@ -1,326 +0,0 @@
|
||||
use super::{GossipUi, ThemeVariant};
|
||||
use crate::comms::ToOverlordMessage;
|
||||
use crate::GLOBALS;
|
||||
use eframe::egui;
|
||||
use egui::widgets::{Button, Slider};
|
||||
use egui::{Align, Context, Layout, ScrollArea, Ui, Vec2};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.heading("Settings");
|
||||
|
||||
ui.with_layout(Layout::bottom_up(Align::Center), |ui| {
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
if ui.button("SAVE CHANGES").clicked() {
|
||||
// Copy local settings to global settings
|
||||
*GLOBALS.settings.write() = app.settings.clone();
|
||||
|
||||
// Tell the overlord to save them
|
||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::SaveSettings);
|
||||
}
|
||||
|
||||
ui.with_layout(Layout::top_down(Align::Min), |ui| {
|
||||
ui.add_space(10.0);
|
||||
ui.separator();
|
||||
|
||||
ScrollArea::vertical()
|
||||
.id_source("settings")
|
||||
.override_scroll_delta(Vec2 { x: 0.0, y: app.current_scroll_offset })
|
||||
.show(ui, |ui| {
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.heading("How Many Relays to Query");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Number of relays to query per person: ").on_hover_text("We will query N relays per person. Many people share the same relays so those will be queried about multiple people. Takes affect on restart. I recommend 2. Too many and gossip will (currently) keep connecting to new relays trying to find the unfindable, loading many events from each. Takes effect on restart.");
|
||||
ui.add(Slider::new(&mut app.settings.num_relays_per_person, 1..=4).text("relays"));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Maximum following feed relays: ")
|
||||
.on_hover_text(
|
||||
"We will not stay connected to more than this many relays for following feed. Takes affect on restart. During these early days of nostr, I recommend capping this at around 20 to 30.",
|
||||
);
|
||||
ui.add(Slider::new(&mut app.settings.max_relays, 5..=100).text("relays"));
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.heading("How Many Posts to Load");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Feed Chunk: ").on_hover_text("This is the amount of time backwards from now that we will load events from. You'll eventually be able to load more, one chunk at a time. Mostly takes effect on restart.");
|
||||
ui.add(Slider::new(&mut app.settings.feed_chunk, 600..=86400).text("seconds, "));
|
||||
ui.label(secs_to_string(app.settings.feed_chunk));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Replies Chunk: ").on_hover_text("This is the amount of time backwards from now that we will load replies, mentions, and DMs from. You'll eventually be able to load more, one chunk at a time. Mostly takes effect on restart.");
|
||||
ui.add(Slider::new(&mut app.settings.replies_chunk, 86400..=2592000).text("seconds, "));
|
||||
ui.label(secs_to_string(app.settings.replies_chunk));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Overlap: ").on_hover_text("If we recently loaded events up to time T, but restarted, we will now load events starting from time T minus overlap. Takes effect on restart. I recommend 300 (5 minutes).");
|
||||
ui.add(Slider::new(&mut app.settings.overlap, 0..=3600).text("seconds, "));
|
||||
ui.label(secs_to_string(app.settings.overlap));
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.heading("Feed");
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.recompute_feed_periodically,
|
||||
"Recompute feed periodically. If this is off, you will get a refresh button"
|
||||
);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Recompute feed every (milliseconds): ")
|
||||
.on_hover_text(
|
||||
"The UI redraws frequently. We recompute the feed less frequently to conserve CPU. Takes effect when the feed next recomputes. I recommend 3500.",
|
||||
);
|
||||
ui.add(Slider::new(&mut app.settings.feed_recompute_interval_ms, 1000..=12000).text("milliseconds"));
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.heading("What Posts to Include");
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.reactions,
|
||||
"Enable reactions (show and react)",
|
||||
);
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.enable_zap_receipts,
|
||||
"Enable zap receipts",
|
||||
);
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.reposts,
|
||||
"Enable reposts (show)",
|
||||
);
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.direct_messages,
|
||||
"Show Direct Messages",
|
||||
)
|
||||
.on_hover_text("Takes effect fully only on restart.");
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.show_long_form,
|
||||
"Show Long-Form Posts",
|
||||
)
|
||||
.on_hover_text("Takes effect fully only on restart.");
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.heading("Post look-and-feel");
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.show_mentions,
|
||||
"Render mentions inline",
|
||||
)
|
||||
.on_hover_text(if app.settings.show_mentions {
|
||||
"Disable to just show a link to a mentioned post where it appears in the text"
|
||||
} else {
|
||||
"Enable to render a mentioned post where it appears in the text"
|
||||
});
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.show_media,
|
||||
"Render all media inline automatically",
|
||||
)
|
||||
.on_hover_text(
|
||||
"If off, you have to click to (potentially fetch and) render media inline. If on, all media referenced by posts in your feed will be (potentially fetched and) rendered. However, if Fetch Media is disabled, only cached media can be shown as media will not be fetched."
|
||||
);
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.heading("Posting");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Proof of Work: ")
|
||||
.on_hover_text("The larger the number, the longer it takes.");
|
||||
ui.add(Slider::new(&mut app.settings.pow, 0..=40).text("leading zero bits"));
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.set_client_tag,
|
||||
"Add tag [\"client\",\"gossip\"] to posts",
|
||||
)
|
||||
.on_hover_text("Takes effect immediately.");
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.heading("Network");
|
||||
|
||||
ui.checkbox(&mut app.settings.offline, "Offline Mode")
|
||||
.on_hover_text("If selected, no network requests will be issued. Takes effect on restart.");
|
||||
|
||||
ui.checkbox(&mut app.settings.automatically_fetch_metadata, "Automatically Fetch Metadata")
|
||||
.on_hover_text("If enabled, metadata that is entirely missing will be fetched as you scroll past people. Existing metadata won't be updated. Takes effect on save.");
|
||||
|
||||
ui.checkbox(&mut app.settings.load_avatars, "Fetch Avatars")
|
||||
.on_hover_text("If disabled, avatars will not be fetched, but cached avatars will still display. Takes effect on save.");
|
||||
|
||||
ui.checkbox(&mut app.settings.load_media, "Fetch Media")
|
||||
.on_hover_text("If disabled, no new media will be fetched, but cached media will still display. Takes effect on save.");
|
||||
|
||||
ui.checkbox(&mut app.settings.check_nip05, "Check NIP-05")
|
||||
.on_hover_text("If disabled, NIP-05 fetches will not be performed, but existing knowledge will be preserved, and following someone by NIP-05 will override this and do the fetch. Takes effect on save.");
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.set_user_agent,
|
||||
&format!("Send User-Agent Header to Relays: gossip/{}", app.about.version),
|
||||
)
|
||||
.on_hover_text("Takes effect on next relay connection.");
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.heading("User Interface");
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.checkbox(
|
||||
&mut app.settings.highlight_unread_events,
|
||||
"Highlight unread events",
|
||||
);
|
||||
ui.checkbox(
|
||||
&mut app.settings.posting_area_at_top,
|
||||
"Show posting area at the top instead of the bottom",
|
||||
);
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Theme:");
|
||||
if !app.settings.theme.follow_os_dark_mode {
|
||||
if app.settings.theme.dark_mode {
|
||||
if ui
|
||||
.add(Button::new("🌙 Dark"))
|
||||
.on_hover_text("Switch to light mode")
|
||||
.clicked()
|
||||
{
|
||||
app.settings.theme.dark_mode = false;
|
||||
super::theme::apply_theme(app.settings.theme, ctx);
|
||||
}
|
||||
} else {
|
||||
if ui
|
||||
.add(Button::new("☀ Light"))
|
||||
.on_hover_text("Switch to dark mode")
|
||||
.clicked()
|
||||
{
|
||||
app.settings.theme.dark_mode = true;
|
||||
super::theme::apply_theme(app.settings.theme, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
let theme_combo = egui::ComboBox::from_id_source("Theme");
|
||||
theme_combo
|
||||
.selected_text( app.settings.theme.name() )
|
||||
.show_ui(ui, |ui| {
|
||||
for theme_variant in ThemeVariant::all() {
|
||||
if ui.add(egui::widgets::SelectableLabel::new(*theme_variant == app.settings.theme.variant, theme_variant.name())).clicked() {
|
||||
app.settings.theme.variant = *theme_variant;
|
||||
super::theme::apply_theme(app.settings.theme, ctx);
|
||||
};
|
||||
}
|
||||
});
|
||||
ui.checkbox(&mut app.settings.theme.follow_os_dark_mode,"Follow OS dark-mode")
|
||||
.on_hover_text("Follow the operating system setting for dark-mode (requires app-restart to take effect)");
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Override DPI: ")
|
||||
.on_hover_text(
|
||||
"On some systems, DPI is not reported properly. In other cases, people like to zoom in or out. This lets you.",
|
||||
);
|
||||
ui.checkbox(
|
||||
&mut app.override_dpi,
|
||||
"Override to ");
|
||||
ui.add(Slider::new(&mut app.override_dpi_value, 72..=250).text("DPI"));
|
||||
if ui.button("Try it now").clicked() {
|
||||
let ppt: f32 = app.override_dpi_value as f32 / 72.0;
|
||||
ctx.set_pixels_per_point(ppt);
|
||||
}
|
||||
|
||||
// transfer to app.settings
|
||||
app.settings.override_dpi = if app.override_dpi {
|
||||
// Set it in settings to be saved on button press
|
||||
Some(app.override_dpi_value)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Maximum FPS: ")
|
||||
.on_hover_text(
|
||||
"The UI redraws every frame. By limiting the maximum FPS you can reduce load on your CPU. Takes effect immediately. I recommend 10, maybe even less.",
|
||||
);
|
||||
ui.add(Slider::new(&mut app.settings.max_fps, 2..=60).text("Frames per second"));
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(12.0);
|
||||
|
||||
if ui.button("Prune Database")
|
||||
.on_hover_text("This will delete events older than six months, but the LMDB files will continue consuming disk space. To compact them, copy withem with `mdb_copy -c` when gossip is not running.")
|
||||
.clicked() {
|
||||
GLOBALS.status_queue.write().write(
|
||||
"Pruning database, please wait (this takes a long time)...".to_owned()
|
||||
);
|
||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PruneDatabase);
|
||||
}
|
||||
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn secs_to_string(secs: u64) -> String {
|
||||
let days = secs / 86400;
|
||||
let remainder = secs % 86400;
|
||||
let hours = remainder / 3600;
|
||||
let remainder = remainder % 3600;
|
||||
let minutes = remainder / 60;
|
||||
let seconds = remainder % 60;
|
||||
let mut output: String = String::new();
|
||||
if days > 0 {
|
||||
output.push_str(&format!(" {} days", days));
|
||||
}
|
||||
if hours > 0 {
|
||||
output.push_str(&format!(" {} hours", hours));
|
||||
}
|
||||
if minutes > 0 {
|
||||
output.push_str(&format!(" {} minutes", minutes));
|
||||
}
|
||||
output.push_str(&format!(" {} seconds", seconds));
|
||||
output
|
||||
}
|
102
src/ui/settings/content.rs
Normal file
102
src/ui/settings/content.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use crate::ui::GossipUi;
|
||||
use eframe::egui;
|
||||
use egui::widgets::Slider;
|
||||
use egui::{Context, Ui};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.heading("Content");
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.heading("Feed Settings");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Feed Chunk: ").on_hover_text("This is the amount of time backwards from now that we will load events from. You'll eventually be able to load more, one chunk at a time. Mostly takes effect on restart.");
|
||||
ui.add(Slider::new(&mut app.settings.feed_chunk, 600..=86400).text("seconds, "));
|
||||
ui.label(secs_to_string(app.settings.feed_chunk));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Replies Chunk: ").on_hover_text("This is the amount of time backwards from now that we will load replies, mentions, and DMs from. You'll eventually be able to load more, one chunk at a time. Mostly takes effect on restart.");
|
||||
ui.add(Slider::new(&mut app.settings.replies_chunk, 86400..=2592000).text("seconds, "));
|
||||
ui.label(secs_to_string(app.settings.replies_chunk));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Overlap: ").on_hover_text("If we recently loaded events up to time T, but restarted, we will now load events starting from time T minus overlap. Takes effect on restart. I recommend 300 (5 minutes).");
|
||||
ui.add(Slider::new(&mut app.settings.overlap, 0..=3600).text("seconds, "));
|
||||
ui.label(secs_to_string(app.settings.overlap));
|
||||
});
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.recompute_feed_periodically,
|
||||
"Recompute feed periodically. If this is off, you will get a refresh button",
|
||||
);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Recompute feed every (milliseconds): ")
|
||||
.on_hover_text(
|
||||
"The UI redraws frequently. We recompute the feed less frequently to conserve CPU. Takes effect when the feed next recomputes. I recommend 3500.",
|
||||
);
|
||||
ui.add(Slider::new(&mut app.settings.feed_recompute_interval_ms, 1000..=12000).text("milliseconds"));
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.heading("Event Selection Settings");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.reactions,
|
||||
"Enable reactions (show and react)",
|
||||
);
|
||||
|
||||
ui.checkbox(&mut app.settings.enable_zap_receipts, "Enable zap receipts");
|
||||
|
||||
ui.checkbox(&mut app.settings.reposts, "Enable reposts (show)");
|
||||
|
||||
ui.checkbox(&mut app.settings.direct_messages, "Show Direct Messages")
|
||||
.on_hover_text("Takes effect fully only on restart.");
|
||||
|
||||
ui.checkbox(&mut app.settings.show_long_form, "Show Long-Form Posts")
|
||||
.on_hover_text("Takes effect fully only on restart.");
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.heading("Event Content Settings");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.checkbox(&mut app.settings.show_mentions, "Render mentions inline")
|
||||
.on_hover_text(if app.settings.show_mentions {
|
||||
"Disable to just show a link to a mentioned post where it appears in the text"
|
||||
} else {
|
||||
"Enable to render a mentioned post where it appears in the text"
|
||||
});
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.show_media,
|
||||
"Render all media inline automatically",
|
||||
)
|
||||
.on_hover_text(
|
||||
"If off, you have to click to (potentially fetch and) render media inline. If on, all media referenced by posts in your feed will be (potentially fetched and) rendered. However, if Fetch Media is disabled, only cached media can be shown as media will not be fetched."
|
||||
);
|
||||
}
|
||||
|
||||
fn secs_to_string(secs: u64) -> String {
|
||||
let days = secs / 86400;
|
||||
let remainder = secs % 86400;
|
||||
let hours = remainder / 3600;
|
||||
let remainder = remainder % 3600;
|
||||
let minutes = remainder / 60;
|
||||
let seconds = remainder % 60;
|
||||
let mut output: String = String::new();
|
||||
if days > 0 {
|
||||
output.push_str(&format!(" {} days", days));
|
||||
}
|
||||
if hours > 0 {
|
||||
output.push_str(&format!(" {} hours", hours));
|
||||
}
|
||||
if minutes > 0 {
|
||||
output.push_str(&format!(" {} minutes", minutes));
|
||||
}
|
||||
output.push_str(&format!(" {} seconds", seconds));
|
||||
output
|
||||
}
|
29
src/ui/settings/database.rs
Normal file
29
src/ui/settings/database.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::comms::ToOverlordMessage;
|
||||
use crate::ui::GossipUi;
|
||||
use crate::GLOBALS;
|
||||
use eframe::egui;
|
||||
use egui::widgets::Slider;
|
||||
use egui::{Context, Ui};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.heading("Database Settings");
|
||||
|
||||
ui.add_space(20.0);
|
||||
ui.label("Prune period");
|
||||
ui.add(Slider::new(&mut app.settings.prune_period_days, 7..=360).text("days"));
|
||||
|
||||
// Only let them prune after they have saved
|
||||
if let Ok(Some(stored_settings)) = GLOBALS.storage.read_settings() {
|
||||
if stored_settings == app.settings {
|
||||
ui.add_space(20.0);
|
||||
if ui.button("Prune Database Now")
|
||||
.on_hover_text("This will delete events older than the prune period. but the LMDB files will continue consuming disk space. To compact them, copy withem with `mdb_copy -c` when gossip is not running.")
|
||||
.clicked() {
|
||||
GLOBALS.status_queue.write().write(
|
||||
"Pruning database, please wait...".to_owned()
|
||||
);
|
||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PruneDatabase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
src/ui/settings/id.rs
Normal file
32
src/ui/settings/id.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use crate::ui::{GossipUi, Page};
|
||||
use eframe::egui;
|
||||
use egui::widgets::Slider;
|
||||
use egui::{Context, Ui};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.heading("Identity Settings");
|
||||
ui.add_space(20.0);
|
||||
|
||||
// public_key
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Public Key:");
|
||||
if let Some(pk) = app.settings.public_key {
|
||||
ui.label(pk.as_bech32_string());
|
||||
} else {
|
||||
ui.label("NOT SET");
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Manage your public key identity on the");
|
||||
if ui.link("Account > Keys").clicked() {
|
||||
app.set_page(Page::YourKeys);
|
||||
}
|
||||
ui.label("page.");
|
||||
});
|
||||
|
||||
// log_n
|
||||
ui.add_space(20.0);
|
||||
ui.label("Encrypted Private Key scrypt N parameter");
|
||||
ui.label("(NOTE: changing this will not re-encrypt any existing encrypted private key)");
|
||||
ui.add(Slider::new(&mut app.settings.log_n, 18..=22).text("logN iteratons"));
|
||||
}
|
99
src/ui/settings/mod.rs
Normal file
99
src/ui/settings/mod.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use crate::comms::ToOverlordMessage;
|
||||
use crate::ui::{GossipUi, SettingsTab};
|
||||
use crate::GLOBALS;
|
||||
use eframe::egui;
|
||||
use egui::{Align, Context, Layout, ScrollArea, Ui, Vec2};
|
||||
|
||||
mod content;
|
||||
mod database;
|
||||
mod id;
|
||||
mod network;
|
||||
mod posting;
|
||||
mod ui;
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.heading("Settings");
|
||||
|
||||
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
|
||||
if let Ok(Some(stored_settings)) = GLOBALS.storage.read_settings() {
|
||||
if stored_settings != app.settings {
|
||||
if ui.button("REVERT CHANGES").clicked() {
|
||||
app.settings = GLOBALS.settings.read().clone();
|
||||
|
||||
// Fully revert any DPI changes
|
||||
match app.settings.override_dpi {
|
||||
Some(value) => {
|
||||
app.override_dpi = true;
|
||||
app.override_dpi_value = value;
|
||||
}
|
||||
None => {
|
||||
app.override_dpi = false;
|
||||
app.override_dpi_value = app.original_dpi_value;
|
||||
}
|
||||
};
|
||||
let ppt: f32 = app.override_dpi_value as f32 / 72.0;
|
||||
ctx.set_pixels_per_point(ppt);
|
||||
}
|
||||
|
||||
if ui.button("SAVE CHANGES").clicked() {
|
||||
// Apply DPI change
|
||||
if stored_settings.override_dpi != app.settings.override_dpi {
|
||||
if let Some(value) = app.settings.override_dpi {
|
||||
let ppt: f32 = value as f32 / 72.0;
|
||||
ctx.set_pixels_per_point(ppt);
|
||||
}
|
||||
}
|
||||
|
||||
// Save new original DPI value
|
||||
if let Some(value) = app.settings.override_dpi {
|
||||
app.original_dpi_value = value;
|
||||
}
|
||||
|
||||
// Copy local settings to global settings
|
||||
*GLOBALS.settings.write() = app.settings.clone();
|
||||
|
||||
// Tell the overlord to save them
|
||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::SaveSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.separator();
|
||||
|
||||
ScrollArea::vertical()
|
||||
.id_source("settings")
|
||||
.override_scroll_delta(Vec2 {
|
||||
x: 0.0,
|
||||
y: app.current_scroll_offset,
|
||||
})
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.selectable_value(&mut app.settings_tab, SettingsTab::Id, "Identity");
|
||||
ui.label("|");
|
||||
ui.selectable_value(&mut app.settings_tab, SettingsTab::Ui, "Ui");
|
||||
ui.label("|");
|
||||
ui.selectable_value(&mut app.settings_tab, SettingsTab::Content, "Content");
|
||||
ui.label("|");
|
||||
ui.selectable_value(&mut app.settings_tab, SettingsTab::Network, "Network");
|
||||
ui.label("|");
|
||||
ui.selectable_value(&mut app.settings_tab, SettingsTab::Posting, "Posting");
|
||||
ui.label("|");
|
||||
ui.selectable_value(&mut app.settings_tab, SettingsTab::Database, "Database");
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.separator();
|
||||
ui.add_space(10.0);
|
||||
|
||||
match app.settings_tab {
|
||||
SettingsTab::Content => content::update(app, ctx, frame, ui),
|
||||
SettingsTab::Database => database::update(app, ctx, frame, ui),
|
||||
SettingsTab::Id => id::update(app, ctx, frame, ui),
|
||||
SettingsTab::Network => network::update(app, ctx, frame, ui),
|
||||
SettingsTab::Posting => posting::update(app, ctx, frame, ui),
|
||||
SettingsTab::Ui => ui::update(app, ctx, frame, ui),
|
||||
}
|
||||
});
|
||||
}
|
183
src/ui/settings/network.rs
Normal file
183
src/ui/settings/network.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use crate::ui::{GossipUi, Page};
|
||||
use eframe::egui;
|
||||
use egui::widgets::Slider;
|
||||
use egui::{Context, Ui};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.heading("Network Settings");
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.checkbox(&mut app.settings.offline, "Offline Mode")
|
||||
.on_hover_text("If selected, no network requests will be issued. Takes effect on restart.");
|
||||
|
||||
ui.checkbox(&mut app.settings.load_avatars, "Fetch Avatars")
|
||||
.on_hover_text("If disabled, avatars will not be fetched, but cached avatars will still display. Takes effect on save.");
|
||||
|
||||
ui.checkbox(&mut app.settings.load_media, "Fetch Media")
|
||||
.on_hover_text("If disabled, no new media will be fetched, but cached media will still display. Takes effect on save.");
|
||||
|
||||
ui.checkbox(&mut app.settings.check_nip05, "Check NIP-05")
|
||||
.on_hover_text("If disabled, NIP-05 fetches will not be performed, but existing knowledge will be preserved, and following someone by NIP-05 will override this and do the fetch. Takes effect on save.");
|
||||
|
||||
ui.checkbox(&mut app.settings.automatically_fetch_metadata, "Automatically Fetch Metadata")
|
||||
.on_hover_text("If enabled, metadata that is entirely missing will be fetched as you scroll past people. Existing metadata won't be updated. Takes effect on save.");
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.heading("Relay Settings");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Manage individual relays on the");
|
||||
if ui.link("Relays > Configure").clicked() {
|
||||
app.set_page(Page::RelaysAll);
|
||||
}
|
||||
ui.label("page.");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Number of relays to query per person: ").on_hover_text("We will query N relays per person. Many people share the same relays so those will be queried about multiple people. Takes affect on restart. I recommend 2. Too many and gossip will (currently) keep connecting to new relays trying to find the unfindable, loading many events from each. Takes effect on restart.");
|
||||
ui.add(Slider::new(&mut app.settings.num_relays_per_person, 1..=4).text("relays"));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Maximum following feed relays: ")
|
||||
.on_hover_text(
|
||||
"We will not stay connected to more than this many relays for following feed. Takes affect on restart. During these early days of nostr, I recommend capping this at around 20 to 30.",
|
||||
);
|
||||
ui.add(Slider::new(&mut app.settings.max_relays, 5..=100).text("relays"));
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.heading("HTTP Fetch Settings");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Looptime for metadata fetcher thread");
|
||||
ui.add(Slider::new(&mut app.settings.fetcher_metadata_looptime_ms, 1000..=6000).text("ms"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Looptime for general fetcher thread");
|
||||
ui.add(Slider::new(&mut app.settings.fetcher_looptime_ms, 1000..=6000).text("ms"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("HTTP Connect Timeout");
|
||||
ui.add(Slider::new(&mut app.settings.fetcher_connect_timeout_sec, 5..=120).text("seconds"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("HTTP Idle Timeout");
|
||||
ui.add(Slider::new(&mut app.settings.fetcher_timeout_sec, 5..=120).text("seconds"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Max simultaneous HTTP requests per remote host")
|
||||
.on_hover_text(
|
||||
"If you set this too high, you may start getting 403-Forbidden or \
|
||||
429-TooManyRequests errors from the remote host",
|
||||
);
|
||||
ui.add(
|
||||
Slider::new(&mut app.settings.fetcher_max_requests_per_host, 1..=10).text("requests"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long to avoid contacting a host after a minor error");
|
||||
ui.add(
|
||||
Slider::new(
|
||||
&mut app.settings.fetcher_host_exclusion_on_low_error_secs,
|
||||
10..=60,
|
||||
)
|
||||
.text("seconds"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long to avoid contacting a host after a medium error");
|
||||
ui.add(
|
||||
Slider::new(
|
||||
&mut app.settings.fetcher_host_exclusion_on_med_error_secs,
|
||||
20..=180,
|
||||
)
|
||||
.text("seconds"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long to avoid contacting a host after a major error");
|
||||
ui.add(
|
||||
Slider::new(
|
||||
&mut app.settings.fetcher_host_exclusion_on_high_error_secs,
|
||||
60..=1800,
|
||||
)
|
||||
.text("seconds"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("When a NIP-11 is not a NIP-11, how many lines of the body do you want to see?");
|
||||
ui.add(
|
||||
Slider::new(&mut app.settings.nip11_lines_to_output_on_error, 1..=100).text("lines"),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.heading("Websocket Settings");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Maximum websocket message size");
|
||||
ui.add(
|
||||
Slider::new(&mut app.settings.max_websocket_message_size_kb, 256..=4096).text("KiB"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Maximum websocket frame size");
|
||||
ui.add(Slider::new(&mut app.settings.max_websocket_frame_size_kb, 256..=4096).text("KiB"));
|
||||
});
|
||||
ui.checkbox(&mut app.settings.websocket_accept_unmasked_frames, "Accept unmasked websocket frames?")
|
||||
.on_hover_text("This is contrary to the standard, but some incorrect software/libraries may use unmasked frames.");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Websocket Connect Timeout");
|
||||
ui.add(
|
||||
Slider::new(&mut app.settings.websocket_connect_timeout_sec, 5..=120).text("seconds"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Websocket Ping Frequency");
|
||||
ui.add(
|
||||
Slider::new(&mut app.settings.websocket_ping_frequency_sec, 30..=600).text("seconds"),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.heading("Stale Time Settings");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long before a relay list becomes stale and needs rechecking?");
|
||||
ui.add(Slider::new(&mut app.settings.relay_list_becomes_stale_hours, 2..=40).text("hours"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long before metadata becomes stale and needs rechecking?");
|
||||
ui.add(Slider::new(&mut app.settings.metadata_becomes_stale_hours, 2..=40).text("hours"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long before valid nip05 becomes stale and needs rechecking?");
|
||||
ui.add(
|
||||
Slider::new(&mut app.settings.nip05_becomes_stale_if_valid_hours, 2..=40).text("hours"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long before invalid nip05 becomes stale and needs rechecking?");
|
||||
ui.add(
|
||||
Slider::new(
|
||||
&mut app.settings.nip05_becomes_stale_if_invalid_minutes,
|
||||
5..=600,
|
||||
)
|
||||
.text("minutes"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long before an avatar image becomes stale and needs rechecking?");
|
||||
ui.add(Slider::new(&mut app.settings.avatar_becomes_stale_hours, 2..=40).text("hours"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("How long before event media becomes stale and needs rechecking?");
|
||||
ui.add(Slider::new(&mut app.settings.media_becomes_stale_hours, 2..=40).text("hours"));
|
||||
});
|
||||
}
|
30
src/ui/settings/posting.rs
Normal file
30
src/ui/settings/posting.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use crate::ui::GossipUi;
|
||||
use eframe::egui;
|
||||
use egui::widgets::Slider;
|
||||
use egui::{Context, Ui};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.heading("Posting Settings");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Proof of Work: ")
|
||||
.on_hover_text("The larger the number, the longer it takes.");
|
||||
ui.add(Slider::new(&mut app.settings.pow, 0..=40).text("leading zero bits"));
|
||||
});
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.set_client_tag,
|
||||
"Add tag [\"client\",\"gossip\"] to posts",
|
||||
)
|
||||
.on_hover_text("Takes effect immediately.");
|
||||
|
||||
ui.checkbox(
|
||||
&mut app.settings.set_user_agent,
|
||||
&format!(
|
||||
"Send User-Agent Header to Relays: gossip/{}",
|
||||
app.about.version
|
||||
),
|
||||
)
|
||||
.on_hover_text("Takes effect on next relay connection.");
|
||||
}
|
91
src/ui/settings/ui.rs
Normal file
91
src/ui/settings/ui.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use crate::ui::{GossipUi, ThemeVariant};
|
||||
use eframe::egui;
|
||||
use egui::widgets::{Button, Slider};
|
||||
use egui::{Context, Ui};
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.heading("UI Settings");
|
||||
|
||||
ui.add_space(20.0);
|
||||
ui.checkbox(
|
||||
&mut app.settings.highlight_unread_events,
|
||||
"Highlight unread events",
|
||||
);
|
||||
ui.checkbox(
|
||||
&mut app.settings.posting_area_at_top,
|
||||
"Show posting area at the top instead of the bottom",
|
||||
);
|
||||
|
||||
ui.add_space(20.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Theme:");
|
||||
if !app.settings.theme.follow_os_dark_mode {
|
||||
if app.settings.theme.dark_mode {
|
||||
if ui
|
||||
.add(Button::new("🌙 Dark"))
|
||||
.on_hover_text("Switch to light mode")
|
||||
.clicked()
|
||||
{
|
||||
app.settings.theme.dark_mode = false;
|
||||
crate::ui::theme::apply_theme(app.settings.theme, ctx);
|
||||
}
|
||||
} else {
|
||||
if ui
|
||||
.add(Button::new("☀ Light"))
|
||||
.on_hover_text("Switch to dark mode")
|
||||
.clicked()
|
||||
{
|
||||
app.settings.theme.dark_mode = true;
|
||||
crate::ui::theme::apply_theme(app.settings.theme, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
let theme_combo = egui::ComboBox::from_id_source("Theme");
|
||||
theme_combo
|
||||
.selected_text( app.settings.theme.name() )
|
||||
.show_ui(ui, |ui| {
|
||||
for theme_variant in ThemeVariant::all() {
|
||||
if ui.add(egui::widgets::SelectableLabel::new(*theme_variant == app.settings.theme.variant, theme_variant.name())).clicked() {
|
||||
app.settings.theme.variant = *theme_variant;
|
||||
crate::ui::theme::apply_theme(app.settings.theme, ctx);
|
||||
};
|
||||
}
|
||||
});
|
||||
ui.checkbox(&mut app.settings.theme.follow_os_dark_mode, "Follow OS dark-mode")
|
||||
.on_hover_text("Follow the operating system setting for dark-mode (requires app-restart to take effect)");
|
||||
});
|
||||
|
||||
ui.add_space(20.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Override DPI: ")
|
||||
.on_hover_text(
|
||||
"On some systems, DPI is not reported properly. In other cases, people like to zoom in or out. This lets you.",
|
||||
);
|
||||
ui.checkbox(
|
||||
&mut app.override_dpi,
|
||||
"Override to ");
|
||||
ui.add(Slider::new(&mut app.override_dpi_value, 72..=250).text("DPI"));
|
||||
|
||||
if ui.button("Apply this change now (without saving)").clicked() {
|
||||
let ppt: f32 = app.override_dpi_value as f32 / 72.0;
|
||||
ctx.set_pixels_per_point(ppt);
|
||||
}
|
||||
|
||||
// transfer to app.settings
|
||||
app.settings.override_dpi = if app.override_dpi {
|
||||
// Set it in settings to be saved on button press
|
||||
Some(app.override_dpi_value)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
});
|
||||
|
||||
ui.add_space(20.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Maximum FPS: ")
|
||||
.on_hover_text(
|
||||
"The UI redraws every frame. By limiting the maximum FPS you can reduce load on your CPU. Takes effect immediately. I recommend 10, maybe even less.",
|
||||
);
|
||||
ui.add(Slider::new(&mut app.settings.max_fps, 2..=60).text("Frames per second"));
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user