feat(commands): relay-fallback search + set_relay_discoverable command
search_directory falls back to relay when local results < 5. Relay stubs upserted with INSERT OR IGNORE to preserve existing data. set_relay_discoverable persists setting and notifies running node. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
parent
cb45e2e463
commit
72a82c1a11
|
|
@ -254,12 +254,15 @@ pub async fn search_directory(
|
|||
query: String,
|
||||
) -> Result<Vec<DirectoryEntry>, String> {
|
||||
ipc_log!("search_directory", {
|
||||
let query_trimmed = query.trim().to_string();
|
||||
|
||||
// local search first
|
||||
let entries = state
|
||||
.storage
|
||||
.load_directory()
|
||||
.map_err(|e| format!("failed to load directory: {}", e))?;
|
||||
|
||||
let query_lower = query.to_lowercase();
|
||||
let query_lower = query_trimmed.to_lowercase();
|
||||
let mut results: Vec<DirectoryEntry> = entries
|
||||
.into_values()
|
||||
.filter(|entry| {
|
||||
|
|
@ -267,8 +270,63 @@ pub async fn search_directory(
|
|||
|| entry.peer_id.to_lowercase().contains(&query_lower)
|
||||
})
|
||||
.collect();
|
||||
|
||||
results.sort_by(|a, b| b.last_seen.cmp(&a.last_seen));
|
||||
|
||||
// relay fallback when local results are sparse
|
||||
if results.len() < 5 && !query_trimmed.is_empty() {
|
||||
let node_handle = state.node_handle.lock().await;
|
||||
if let Some(ref handle) = *node_handle {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let _ = handle
|
||||
.command_tx
|
||||
.send(crate::node::NodeCommand::DirectorySearch {
|
||||
query: query_trimmed.clone(),
|
||||
reply: tx,
|
||||
})
|
||||
.await;
|
||||
drop(node_handle);
|
||||
|
||||
// wait up to 5 seconds for relay response
|
||||
if let Ok(Ok(Ok(relay_entries))) =
|
||||
tokio::time::timeout(std::time::Duration::from_secs(5), rx).await
|
||||
{
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as u64;
|
||||
|
||||
for entry in relay_entries {
|
||||
// upsert as stub — empty bio/public_key means never directly connected
|
||||
let stub = DirectoryEntry {
|
||||
peer_id: entry.peer_id.clone(),
|
||||
display_name: entry.display_name,
|
||||
bio: String::new(),
|
||||
public_key: String::new(),
|
||||
last_seen: entry.last_seen.saturating_mul(1000).max(now - 86_400_000),
|
||||
is_friend: false,
|
||||
};
|
||||
// preserve existing local data if we already know this peer
|
||||
let _ = state.storage.save_directory_entry_if_new(&stub);
|
||||
}
|
||||
|
||||
// re-run local search to get merged results
|
||||
let entries2 = state
|
||||
.storage
|
||||
.load_directory()
|
||||
.unwrap_or_default();
|
||||
let mut results2: Vec<DirectoryEntry> = entries2
|
||||
.into_values()
|
||||
.filter(|entry| {
|
||||
entry.display_name.to_lowercase().contains(&query_lower)
|
||||
|| entry.peer_id.to_lowercase().contains(&query_lower)
|
||||
})
|
||||
.collect();
|
||||
results2.sort_by(|a, b| b.last_seen.cmp(&a.last_seen));
|
||||
return Ok(results2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
})
|
||||
}
|
||||
|
|
@ -329,6 +387,33 @@ pub async fn discover_global_peers(state: State<'_, AppState>) -> Result<(), Str
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// toggle relay discoverability at runtime and sync the setting to disk
|
||||
#[tauri::command]
|
||||
pub async fn set_relay_discoverable(
|
||||
state: State<'_, AppState>,
|
||||
enabled: bool,
|
||||
) -> Result<(), String> {
|
||||
ipc_log!("set_relay_discoverable", {
|
||||
// persist setting
|
||||
let mut settings = state.storage.load_settings().unwrap_or_default();
|
||||
settings.relay_discoverable = enabled;
|
||||
state
|
||||
.storage
|
||||
.save_settings(&settings)
|
||||
.map_err(|e| format!("failed to save settings: {}", e))?;
|
||||
|
||||
// notify running node
|
||||
let node_handle = state.node_handle.lock().await;
|
||||
if let Some(ref handle) = *node_handle {
|
||||
let _ = handle
|
||||
.command_tx
|
||||
.send(crate::node::NodeCommand::SetRelayDiscoverable { enabled })
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
// change relay address and restart the node
|
||||
// used when default relay is unreachable or at capacity
|
||||
#[tauri::command]
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ pub fn run() {
|
|||
commands::identity::add_friend,
|
||||
commands::identity::remove_friend,
|
||||
commands::identity::discover_global_peers,
|
||||
commands::identity::set_relay_discoverable,
|
||||
commands::identity::set_relay_address,
|
||||
commands::identity::reset_identity,
|
||||
commands::identity::cache_avatar_icon,
|
||||
|
|
|
|||
|
|
@ -744,6 +744,27 @@ impl DiskStorage {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// save a directory entry only if the peer is not already known
|
||||
// preserves existing data (bio, public_key) when upserting relay stubs
|
||||
pub fn save_directory_entry_if_new(&self, entry: &DirectoryEntry) -> Result<(), io::Error> {
|
||||
let conn = self.open_conn()?;
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO directory_entries (
|
||||
peer_id, display_name, bio, public_key, last_seen, is_friend
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
params![
|
||||
entry.peer_id,
|
||||
entry.display_name,
|
||||
entry.bio,
|
||||
entry.public_key,
|
||||
entry.last_seen as i64,
|
||||
if entry.is_friend { 1_i64 } else { 0_i64 }
|
||||
],
|
||||
)
|
||||
.map_err(sqlite_to_io_error)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// load the entire peer directory
|
||||
pub fn load_directory(&self) -> Result<HashMap<String, DirectoryEntry>, io::Error> {
|
||||
let conn = self.open_conn()?;
|
||||
|
|
|
|||
Loading…
Reference in New Issue