From cd5658f01da34e52674b881ef8eb4f5735f09704 Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 2 Aug 2023 22:25:54 +0800 Subject: [PATCH] refactor addressbook sync Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_card.dart | 13 -- flutter/lib/models/ab_model.dart | 150 ++++++++++++++++++---- flutter/lib/models/peer_model.dart | 16 +++ libs/hbb_common/Cargo.toml | 3 +- libs/hbb_common/src/config.rs | 108 ++++++++++++++-- libs/hbb_common/src/password_security.rs | 62 +++++++-- src/client.rs | 20 ++- src/flutter_ffi.rs | 43 ++++++- src/ui.rs | 2 +- src/ui_interface.rs | 14 +- 10 files changed, 365 insertions(+), 66 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index a73454040..eea4812e4 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -500,7 +500,6 @@ abstract class BasePeerCard extends StatelessWidget { return await _isForceAlwaysRelay(id); }, setter: (bool v) async { - gFFI.abModel.setPeerForceAlwaysRelay(id, v); await bind.mainSetPeerOption( id: id, key: option, value: bool2option(option, v)); }, @@ -671,7 +670,6 @@ abstract class BasePeerCard extends StatelessWidget { isInProgress.value = true; String name = controller.text.trim(); await bind.mainSetPeerAlias(id: id, alias: name); - gFFI.abModel.setPeerAlias(id, name); _update(); close(); isInProgress.value = false; @@ -968,16 +966,6 @@ class AddressBookPeerCard extends BasePeerCard { return menuItems; } - @protected - @override - Future _isForceAlwaysRelay(String id) async => - gFFI.abModel.find(id)?.forceAlwaysRelay ?? false; - - @protected - @override - Future _getAlias(String id) async => - gFFI.abModel.find(id)?.alias ?? ''; - @protected @override void _update() => gFFI.abModel.pullAb(); @@ -1126,7 +1114,6 @@ void _rdpDialog(String id) async { id: id, key: 'rdp_username', value: username); await bind.mainSetPeerOption( id: id, key: 'rdp_password', value: password); - gFFI.abModel.setRdp(id, port, username); close(); } diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 3df43546a..2562a0b0e 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -31,12 +32,21 @@ class AbModel { final selectedTags = List.empty(growable: true).obs; var initialized = false; var licensedDevices = 0; + var sync_all_from_recent = true; + var _timerCounter = 0; WeakReference parent; - AbModel(this.parent); + AbModel(this.parent) { + if (desktopType == DesktopType.main) { + Timer.periodic(Duration(milliseconds: 500), (timer) async { + if (_timerCounter++ % 6 == 0) syncFromRecent(); + }); + } + } Future pullAb({force = true, quiet = false}) async { + debugPrint("pullAb, force:$force, quite:$quiet"); if (gFFI.userModel.userName.isEmpty) return; if (abLoading.value) return; if (!force && initialized) return; @@ -75,18 +85,24 @@ class AbModel { } } } catch (err) { + reset(); abError.value = err.toString(); } finally { abLoading.value = false; initialized = true; + sync_all_from_recent = true; + _timerCounter = 0; + save(); } } Future reset() async { + abError.value = ''; await bind.mainSetLocalOption(key: "selected-tags", value: ''); tags.clear(); peers.clear(); initialized = false; + await bind.mainClearAb(); } void addId(String id, String alias, List tags) { @@ -131,10 +147,11 @@ class AbModel { } Future pushAb() async { + debugPrint("pushAb"); final api = "${await bind.mainGetApiServer()}/api/ab"; var authHeaders = getHttpHeaders(); authHeaders['Content-Type'] = "application/json"; - final peersJsonData = peers.map((e) => e.toJson()).toList(); + final peersJsonData = peers.map((e) => e.toAbUploadJson()).toList(); final body = jsonEncode({ "data": jsonEncode({"tags": tags, "peers": peersJsonData}) }); @@ -149,10 +166,14 @@ class AbModel { request.headers.addAll(authHeaders); try { await http.Client().send(request); - await pullAb(quiet: true); + // await pullAb(quiet: true); } catch (e) { BotToast.showText(contentColor: Colors.red, text: e.toString()); - } finally {} + } finally { + sync_all_from_recent = true; + _timerCounter = 0; + save(); + } } Peer? find(String id) { @@ -197,28 +218,111 @@ class AbModel { } } - Future setPeerAlias(String id, String value) async { - final it = peers.where((p0) => p0.id == id); - if (it.isNotEmpty) { - it.first.alias = value; - await pushAb(); + void syncFromRecent() async { + Peer merge(Peer r, Peer p) { + return Peer( + id: p.id, + hash: r.hash.isEmpty ? p.hash : r.hash, + username: r.username.isEmpty ? p.username : r.username, + hostname: r.hostname.isEmpty ? p.hostname : r.hostname, + platform: r.platform.isEmpty ? p.platform : r.platform, + alias: r.alias, + tags: p.tags, + forceAlwaysRelay: r.forceAlwaysRelay, + rdpPort: r.rdpPort, + rdpUsername: r.rdpUsername); + } + + bool shouldSync(Peer a, Peer b) { + return a.hash != b.hash || + a.username != b.username || + a.platform != b.platform || + a.hostname != b.hostname; + } + + Future> getRecentPeers() async { + try { + if (peers.isEmpty) []; + List filteredPeerIDs; + if (sync_all_from_recent) { + sync_all_from_recent = false; + filteredPeerIDs = peers.map((e) => e.id).toList(); + } else { + final new_stored_str = await bind.mainGetNewStoredPeers(); + if (new_stored_str.isEmpty) return []; + List new_stores = + (jsonDecode(new_stored_str) as List) + .map((e) => e.toString()) + .toList(); + final abPeerIds = peers.map((e) => e.id).toList(); + filteredPeerIDs = + new_stores.where((e) => abPeerIds.contains(e)).toList(); + } + if (filteredPeerIDs.isEmpty) return []; + final loadStr = await bind.mainLoadRecentPeersForAb( + filter: jsonEncode(filteredPeerIDs)); + if (loadStr.isEmpty) { + return []; + } + List mapPeers = jsonDecode(loadStr); + List recents = List.empty(growable: true); + for (var m in mapPeers) { + if (m is Map) { + recents.add(Peer.fromJson(m)); + } + } + return recents; + } catch (e) { + debugPrint('getRecentPeers:$e'); + } + return []; + } + + try { + if (!shouldSyncAb()) return; + final oldPeers = peers.toList(); + final recents = await getRecentPeers(); + if (recents.isEmpty) return; + for (var i = 0; i < peers.length; i++) { + var p = peers[i]; + var r = recents.firstWhereOrNull((r) => p.id == r.id); + if (r != null) { + peers[i] = merge(r, p); + } + } + bool changed = false; + for (var i = 0; i < peers.length; i++) { + final o = oldPeers[i]; + final p = peers[i]; + if (shouldSync(o, p)) { + changed = true; + break; + } + } + // Be careful with loop calls + if (changed) { + pushAb(); + } + } catch (e) { + debugPrint('syncFromRecent:$e'); } } - Future setPeerForceAlwaysRelay(String id, bool value) async { - final it = peers.where((p0) => p0.id == id); - if (it.isNotEmpty) { - it.first.forceAlwaysRelay = value; - await pushAb(); - } - } - - Future setRdp(String id, String port, String username) async { - final it = peers.where((p0) => p0.id == id); - if (it.isNotEmpty) { - it.first.rdpPort = port; - it.first.rdpUsername = username; - await pushAb(); + save() { + try { + final infos = peers + .map((e) => ({ + "id": e.id, + "hash": e.hash, + })) + .toList(); + final m = { + "access_token": bind.mainGetLocalOption(key: 'access_token'), + "peers": infos, + }; + bind.mainSaveAb(json: jsonEncode(m)); + } catch (e) { + debugPrint('ab save:$e'); } } } diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index 596355adc..7a61c3dcf 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -4,6 +4,7 @@ import 'platform_model.dart'; class Peer { final String id; + String hash; final String username; final String hostname; final String platform; @@ -23,6 +24,7 @@ class Peer { Peer.fromJson(Map json) : id = json['id'] ?? '', + hash = json['hash'] ?? '', username = json['username'] ?? '', hostname = json['hostname'] ?? '', platform = json['platform'] ?? '', @@ -35,6 +37,7 @@ class Peer { Map toJson() { return { "id": id, + "hash": hash, "username": username, "hostname": hostname, "platform": platform, @@ -46,8 +49,20 @@ class Peer { }; } + Map toAbUploadJson() { + return { + "id": id, + "hash": hash, + "username": username, + "hostname": hostname, + "platform": platform, + "tags": tags, + }; + } + Peer({ required this.id, + required this.hash, required this.username, required this.hostname, required this.platform, @@ -61,6 +76,7 @@ class Peer { Peer.loading() : this( id: '...', + hash: '', username: '...', hostname: '...', platform: '...', diff --git a/libs/hbb_common/Cargo.toml b/libs/hbb_common/Cargo.toml index 4d008a866..5c93ccd04 100644 --- a/libs/hbb_common/Cargo.toml +++ b/libs/hbb_common/Cargo.toml @@ -24,6 +24,7 @@ directories-next = "2.0" rand = "0.8" serde_derive = "1.0" serde = "1.0" +serde_json = "1.0" lazy_static = "1.4" confy = { git = "https://github.com/open-trade/confy" } dirs-next = "2.0" @@ -56,5 +57,3 @@ winapi = { version = "0.3", features = ["winuser"] } [target.'cfg(target_os = "macos")'.dependencies] osascript = "0.3" -[dev-dependencies] -serde_json = "1.0" diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 6d3a26c51..36709d8c5 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1,6 +1,7 @@ use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, fs, + io::{Read, Write}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, ops::{Deref, DerefMut}, path::{Path, PathBuf}, @@ -13,10 +14,12 @@ use rand::Rng; use regex::Regex; use serde as de; use serde_derive::{Deserialize, Serialize}; +use serde_json; use sodiumoxide::base64; use sodiumoxide::crypto::sign; use crate::{ + compress::{compress, decompress}, log, password_security::{ decrypt_str_or_original, decrypt_vec_or_original, encrypt_str_or_original, @@ -31,6 +34,7 @@ pub const REG_INTERVAL: i64 = 12_000; pub const COMPRESS_LEVEL: i32 = 3; const SERIAL: i32 = 3; const PASSWORD_ENC_VERSION: &str = "00"; +const ENCRYPT_MAX_LEN: usize = 128; // config2 options #[cfg(target_os = "linux")] @@ -57,6 +61,7 @@ lazy_static::lazy_static! { pub static ref APP_NAME: Arc> = Arc::new(RwLock::new("RustDesk".to_owned())); static ref KEY_PAIR: Arc>> = Default::default(); static ref USER_DEFAULT_CONFIG: Arc> = Arc::new(RwLock::new((UserDefaultConfig::load(), Instant::now()))); + pub static ref NEW_STORED_PEER_CONFIG: Arc>> = Default::default(); } lazy_static::lazy_static! { @@ -376,7 +381,8 @@ impl Config2 { fn store(&self) { let mut config = self.clone(); if let Some(mut socks) = config.socks { - socks.password = encrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION); + socks.password = + encrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN); config.socks = Some(socks); } Config::store_(&config, "2"); @@ -485,8 +491,9 @@ impl Config { fn store(&self) { let mut config = self.clone(); - config.password = encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION); - config.enc_id = encrypt_str_or_original(&config.id, PASSWORD_ENC_VERSION); + config.password = + encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN); + config.enc_id = encrypt_str_or_original(&config.id, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN); config.id = "".to_owned(); Config::store_(&config, ""); } @@ -980,15 +987,17 @@ impl PeerConfig { pub fn store(&self, id: &str) { let _lock = CONFIG.read().unwrap(); let mut config = self.clone(); - config.password = encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION); + config.password = + encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN); for opt in ["rdp_password", "os-username", "os-password"] { if let Some(v) = config.options.get_mut(opt) { - *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION) + *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN) } } if let Err(err) = store_path(Self::path(id), config) { log::error!("Failed to store config: {}", err); } + NEW_STORED_PEER_CONFIG.lock().unwrap().insert(id.to_owned()); } pub fn remove(id: &str) { @@ -1014,7 +1023,7 @@ impl PeerConfig { Config::with_extension(Config::path(path)) } - pub fn peers() -> Vec<(String, SystemTime, PeerConfig)> { + pub fn peers(id_filters: Option>) -> Vec<(String, SystemTime, PeerConfig)> { if let Ok(peers) = Config::path(PEERS).read_dir() { if let Ok(peers) = peers .map(|res| res.map(|e| e.path())) @@ -1027,7 +1036,6 @@ impl PeerConfig { && p.extension().map(|p| p.to_str().unwrap_or("")) == Some("toml") }) .map(|p| { - let t = crate::get_modified_time(p); let id = p .file_stem() .map(|p| p.to_str().unwrap_or("")) @@ -1041,12 +1049,21 @@ impl PeerConfig { } else { id }; - - let c = PeerConfig::load(&id_decoded_string); + (id_decoded_string, p) + }) + .filter(|(id, _)| { + let Some(filters) = &id_filters else { + return true; + }; + filters.contains(id) + }) + .map(|(id, p)| { + let t = crate::get_modified_time(p); + let c = PeerConfig::load(&id); if c.info.platform.is_empty() { fs::remove_file(p).ok(); } - (id_decoded_string, t, c) + (id, t, c) }) .filter(|p| !p.2.info.platform.is_empty()) .collect(); @@ -1445,6 +1462,74 @@ impl UserDefaultConfig { } } +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct AbPeer { + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub id: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub hash: String, +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct Ab { + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub access_token: String, + #[serde(default, deserialize_with = "deserialize_vec_abpeer")] + pub peers: Vec, +} + +impl Ab { + fn path() -> PathBuf { + let filename = format!("{}_ab", APP_NAME.read().unwrap().clone()); + Config::path(filename) + } + + pub fn store(json: String) { + if let Ok(mut file) = std::fs::File::create(Self::path()) { + let data = compress(json.as_bytes()); + let max_len = 32 * 1024 * 1024; + if data.len() > max_len { + // not store original + return; + } + let data = encrypt_vec_or_original(&data, PASSWORD_ENC_VERSION, max_len); + file.write_all(&data).ok(); + }; + } + + pub fn load() -> Ab { + if let Ok(mut file) = std::fs::File::open(Self::path()) { + let mut data = vec![]; + if file.read_to_end(&mut data).is_ok() { + let (data, succ, _) = decrypt_vec_or_original(&data, PASSWORD_ENC_VERSION); + if succ { + let data = decompress(&data); + if let Ok(ab) = serde_json::from_str::(&String::from_utf8_lossy(&data)) { + return ab; + } + } + } + }; + Ab::default() + } + + pub fn remove() { + std::fs::remove_file(Self::path()).ok(); + } +} + // use default value when field type is wrong macro_rules! deserialize_default { ($func_name:ident, $return_type:ty) => { @@ -1464,6 +1549,7 @@ deserialize_default!(deserialize_vec_u8, Vec); deserialize_default!(deserialize_vec_string, Vec); deserialize_default!(deserialize_vec_i32_string_i32, Vec<(i32, String, i32)>); deserialize_default!(deserialize_vec_discoverypeer, Vec); +deserialize_default!(deserialize_vec_abpeer, Vec); deserialize_default!(deserialize_keypair, KeyPair); deserialize_default!(deserialize_size, Size); deserialize_default!(deserialize_hashmap_string_string, HashMap); diff --git a/libs/hbb_common/src/password_security.rs b/libs/hbb_common/src/password_security.rs index 5aca2c85a..a127ccd2b 100644 --- a/libs/hbb_common/src/password_security.rs +++ b/libs/hbb_common/src/password_security.rs @@ -84,13 +84,13 @@ pub fn hide_cm() -> bool { const VERSION_LEN: usize = 2; -pub fn encrypt_str_or_original(s: &str, version: &str) -> String { +pub fn encrypt_str_or_original(s: &str, version: &str, max_len: usize) -> String { if decrypt_str_or_original(s, version).1 { log::error!("Duplicate encryption!"); return s.to_owned(); } if version == "00" { - if let Ok(s) = encrypt(s.as_bytes()) { + if let Ok(s) = encrypt(s.as_bytes(), max_len) { return version.to_owned() + &s; } } @@ -117,13 +117,13 @@ pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool, (s.to_owned(), false, !s.is_empty()) } -pub fn encrypt_vec_or_original(v: &[u8], version: &str) -> Vec { +pub fn encrypt_vec_or_original(v: &[u8], version: &str, max_len: usize) -> Vec { if decrypt_vec_or_original(v, version).1 { log::error!("Duplicate encryption!"); return v.to_owned(); } if version == "00" { - if let Ok(s) = encrypt(v) { + if let Ok(s) = encrypt(v, max_len) { let mut version = version.to_owned().into_bytes(); version.append(&mut s.into_bytes()); return version; @@ -148,8 +148,8 @@ pub fn decrypt_vec_or_original(v: &[u8], current_version: &str) -> (Vec, boo (v.to_owned(), false, !v.is_empty()) } -fn encrypt(v: &[u8]) -> Result { - if !v.is_empty() && v.len() <= 128 { +fn encrypt(v: &[u8], max_len: usize) -> Result { + if !v.is_empty() && v.len() <= max_len { symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original)) } else { Err(()) @@ -185,12 +185,15 @@ mod test { #[test] fn test() { use super::*; + use rand::{thread_rng, Rng}; + use std::time::Instant; let version = "00"; + let max_len = 128; println!("test str"); let data = "Hello World"; - let encrypted = encrypt_str_or_original(data, version); + let encrypted = encrypt_str_or_original(data, version, max_len); let (decrypted, succ, store) = decrypt_str_or_original(&encrypted, version); println!("data: {data}"); println!("encrypted: {encrypted}"); @@ -202,11 +205,14 @@ mod test { let (_, _, store) = decrypt_str_or_original(&encrypted, "99"); assert!(store); assert!(!decrypt_str_or_original(&decrypted, version).1); - assert_eq!(encrypt_str_or_original(&encrypted, version), encrypted); + assert_eq!( + encrypt_str_or_original(&encrypted, version, max_len), + encrypted + ); println!("test vec"); let data: Vec = vec![1, 2, 3, 4, 5, 6]; - let encrypted = encrypt_vec_or_original(&data, version); + let encrypted = encrypt_vec_or_original(&data, version, max_len); let (decrypted, succ, store) = decrypt_vec_or_original(&encrypted, version); println!("data: {data:?}"); println!("encrypted: {encrypted:?}"); @@ -218,7 +224,10 @@ mod test { let (_, _, store) = decrypt_vec_or_original(&encrypted, "99"); assert!(store); assert!(!decrypt_vec_or_original(&decrypted, version).1); - assert_eq!(encrypt_vec_or_original(&encrypted, version), encrypted); + assert_eq!( + encrypt_vec_or_original(&encrypted, version, max_len), + encrypted + ); println!("test original"); let data = version.to_string() + "Hello World"; @@ -238,5 +247,38 @@ mod test { let (_, succ, store) = decrypt_vec_or_original(&[], version); assert!(!store); assert!(!succ); + + println!("test speed"); + let test_speed = |len: usize, name: &str| { + let mut data: Vec = vec![]; + let mut rng = thread_rng(); + for _ in 0..len { + data.push(rng.gen_range(0..255)); + } + let start: Instant = Instant::now(); + let encrypted = encrypt_vec_or_original(&data, version, len); + assert_ne!(data, decrypted); + let t1 = start.elapsed(); + let start = Instant::now(); + let (decrypted, _, _) = decrypt_vec_or_original(&encrypted, version); + let t2 = start.elapsed(); + assert_eq!(data, decrypted); + println!("{name}"); + println!("encrypt:{:?}, decrypt:{:?}", t1, t2); + + let start: Instant = Instant::now(); + let encrypted = base64::encode(&data, base64::Variant::Original); + let t1 = start.elapsed(); + let start = Instant::now(); + let decrypted = base64::decode(&encrypted, base64::Variant::Original).unwrap(); + let t2 = start.elapsed(); + assert_eq!(data, decrypted); + println!("base64, encrypt:{:?}, decrypt:{:?}", t1, t2,); + }; + test_speed(128, "128"); + test_speed(1024, "1k"); + test_speed(1024 * 1024, "1M"); + test_speed(10 * 1024 * 1024, "10M"); + test_speed(100 * 1024 * 1024, "100M"); } } diff --git a/src/client.rs b/src/client.rs index e98e6bd3a..dfd73455c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -34,7 +34,8 @@ use hbb_common::{ anyhow::{anyhow, Context}, bail, config::{ - Config, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT, + Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT, READ_TIMEOUT, + RELAY_PORT, }, get_version_number, log, message_proto::{option_message::BoolOption, *}, @@ -42,6 +43,7 @@ use hbb_common::{ rand, rendezvous_proto::*, socket_client, + sodiumoxide::base64, sodiumoxide::crypto::{box_, secretbox, sign}, tcp::FramedStream, timeout, @@ -2235,6 +2237,22 @@ pub async fn handle_hash( if password.is_empty() { password = lc.read().unwrap().config.password.clone(); } + if password.is_empty() { + let access_token = LocalConfig::get_option("access_token"); + let ab = hbb_common::config::Ab::load(); + if !access_token.is_empty() && access_token == ab.access_token { + let id = lc.read().unwrap().id.clone(); + if let Some(p) = ab + .peers + .iter() + .find_map(|p| if p.id == id { Some(p) } else { None }) + { + if let Ok(hash) = base64::decode(p.hash.clone(), base64::Variant::Original) { + password = hash; + } + } + } + } let password = if password.is_empty() { // login without password, the remote side can click accept interface.msgbox("input-password", "Password Required", "", ""); diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 64a221141..8925b5804 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -618,7 +618,7 @@ pub fn main_show_option(_key: String) -> SyncReturn { #[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))] if _key.eq(config::CONFIG_OPTION_ALLOW_LINUX_HEADLESS) { - return SyncReturn(true) + return SyncReturn(true); } SyncReturn(false) } @@ -777,6 +777,15 @@ pub fn main_set_peer_alias(id: String, alias: String) { set_peer_option(id, "alias".to_owned(), alias) } +pub fn main_get_new_stored_peers() -> String { + let peers: Vec = config::NEW_STORED_PEER_CONFIG + .lock() + .unwrap() + .drain() + .collect(); + serde_json::to_string(&peers).unwrap_or_default() +} + pub fn main_forget_password(id: String) { forget_password(id) } @@ -787,7 +796,7 @@ pub fn main_peer_has_password(id: String) -> bool { pub fn main_load_recent_peers() { if !config::APP_DIR.read().unwrap().is_empty() { - let peers: Vec> = PeerConfig::peers() + let peers: Vec> = PeerConfig::peers(None) .drain(..) .map(|(id, _, p)| peer_to_map(id, p)) .collect(); @@ -808,7 +817,7 @@ pub fn main_load_recent_peers() { pub fn main_load_recent_peers_sync() -> SyncReturn { if !config::APP_DIR.read().unwrap().is_empty() { - let peers: Vec> = PeerConfig::peers() + let peers: Vec> = PeerConfig::peers(None) .drain(..) .map(|(id, _, p)| peer_to_map(id, p)) .collect(); @@ -825,10 +834,22 @@ pub fn main_load_recent_peers_sync() -> SyncReturn { SyncReturn("".to_string()) } +pub fn main_load_recent_peers_for_ab(filter: String) -> String { + let id_filters = serde_json::from_str::>(&filter).unwrap_or_default(); + if !config::APP_DIR.read().unwrap().is_empty() { + let peers: Vec> = PeerConfig::peers(Some(id_filters)) + .drain(..) + .map(|(id, _, p)| peer_to_map_ab(id, p)) + .collect(); + return serde_json::ser::to_string(&peers).unwrap_or("".to_owned()); + } + "".to_string() +} + pub fn main_load_fav_peers() { if !config::APP_DIR.read().unwrap().is_empty() { let favs = get_fav(); - let mut recent = PeerConfig::peers(); + let mut recent = PeerConfig::peers(None); let mut lan = config::LanPeers::load() .peers .iter() @@ -1086,6 +1107,20 @@ pub fn main_start_dbus_server() { } } +pub fn main_save_ab(json: String) { + if json.len() > 1024 { + std::thread::spawn(|| { + config::Ab::store(json); + }); + } else { + config::Ab::store(json); + } +} + +pub fn main_clear_ab() { + config::Ab::remove(); +} + pub fn session_send_pointer(session_id: SessionID, msg: String) { if let Ok(m) = serde_json::from_str::>(&msg) { let alt = m.get("alt").is_some(); diff --git a/src/ui.rs b/src/ui.rs index 5554a3020..8f036509b 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -412,7 +412,7 @@ impl UI { fn get_recent_sessions(&mut self) -> Value { // to-do: limit number of recent sessions, and remove old peer file - let peers: Vec = PeerConfig::peers() + let peers: Vec = PeerConfig::peers(None) .drain(..) .map(|p| Self::get_peer_value(p.0, p.2)) .collect(); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index c620f4f91..512589b6e 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -3,7 +3,9 @@ use hbb_common::password_security; use hbb_common::{ allow_err, config::{self, Config, LocalConfig, PeerConfig}, - directories_next, log, tokio, + directories_next, log, + sodiumoxide::base64, + tokio, }; use hbb_common::{ bytes::Bytes, @@ -610,6 +612,16 @@ pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> { ]) } +#[cfg(feature = "flutter")] +pub fn peer_to_map_ab(id: String, p: PeerConfig) -> HashMap<&'static str, String> { + let mut m = peer_to_map(id, p.clone()); + m.insert( + "hash", + base64::encode(p.password, base64::Variant::Original), + ); + m +} + #[inline] pub fn get_lan_peers() -> Vec> { config::LanPeers::load()