Merge pull request 'add events wrapping enum' (#18) from events into main

Reviewed-on: #18
Reviewed-by: Sarah Jamie Lewis <sarah@openprivacy.ca>
This commit is contained in:
Sarah Jamie Lewis 2022-07-21 15:56:24 +00:00
commit ec4e6f4c22
7 changed files with 1161 additions and 118 deletions

90
Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "block-buffer"
version = "0.10.2"
@ -17,6 +23,19 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
[[package]]
name = "cpufeatures"
version = "0.2.1"
@ -125,14 +144,35 @@ checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
name = "libcwtch"
version = "0.3.2"
dependencies = [
"chrono",
"hex-literal",
"libc",
"serde",
"serde_json",
"serde_repr",
"serde_with",
"sha2",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
@ -194,6 +234,17 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_with"
version = "1.11.0"
@ -245,6 +296,17 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "typenum"
version = "1.15.0"
@ -262,3 +324,31 @@ name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -18,3 +18,5 @@ libc = "0.2"
serde_json = "1.0"
serde = { version = "1.0.127", features = ["derive"] }
serde_with = { version = "1.10.0" }
serde_repr = "0.1"
chrono = "0.4.19"

View File

@ -3,6 +3,7 @@ use std::thread;
use libcwtch;
use libcwtch::structs::*;
use libcwtch::CwtchLib;
use libcwtch::event::Event;
fn main() {
let bot_home = "example_cwtch_dir";
@ -19,46 +20,30 @@ fn main() {
let event_loop_handle = thread::spawn(move || {
loop {
let event_str = cwtch.get_appbus_event();
println!("event: {}", event_str);
let event = cwtch.get_appbus_event();
println!("event: {:?}", event);
let event: CwtchEvent =
serde_json::from_str(&event_str).expect("Error parsing Cwtch event");
match event.event_type.as_str() {
"CwtchStarted" => {
match event {
Event::CwtchStarted => {
println!("event CwtchStarted!");
println!("Creating bot");
cwtch.create_profile("Echobot", "be gay do crime");
}
"NewPeer" => {
Event::NewPeer { profile_id, tag, created, name, default_picture, picture, online, profile_data } => {
println!(
"\n***** {} at {} *****\n",
event.data["name"], event.data["Identity"]
name, profile_id.as_str()
);
// process json for profile, contacts and servers...else {
let profile = match Profile::new(
&event.data["Identity"],
&event.data["name"],
&event.data["picture"],
&event.data["ContactsJson"],
&event.data["ServerList"],
) {
Ok(p) => p,
Err(e) => panic!("error parsing profile: {}", e),
};
let profile = profile_data;
print!("profile: {:?}", profile);
}
"NewMessageFromPeer" => {
let to = &event.data["ProfileOnion"];
let conversation_id = event.data["ConversationID"].parse::<i32>().unwrap();
let message: Message =
serde_json::from_str(&event.data["Data"]).expect("Error parsing message");
let response = Message { o: 1, d: message.d };
Event::NewMessageFromPeer { profile_id, conversation_id, contact_id: contact, nick, timestamp_received, message, notification, picture } => {
let response = Message { o: message.o.into(), d: message.d };
let response_json =
serde_json::to_string(&response).expect("Error parsing json response");
cwtch.send_message(&to, conversation_id, &response_json);
cwtch.send_message( &profile_id, conversation_id, &response_json);
}
_ => eprintln!("unhandled event!"),
};

View File

@ -8,7 +8,8 @@ use std::ffi::CString;
use super::CwtchLib;
use crate::cwtchlib_go::bindings;
use crate::{CwtchError, structs::*};
use crate::{ConversationID, CwtchError, FileKey, ProfileIdentity, ServerIdentity, structs::*};
use crate::event::Event;
struct c_str_wrap {
raw: *mut i8,
@ -86,42 +87,113 @@ pub struct CwtchLibGo {}
impl CwtchLibGo {
c_bind!(_get_profile_attribute(profile: &str, key: &str;;) c_GetProfileAttribute -> String);
c_bind!(_get_conversation_attribute(profile: &str; conversation_id: i32; key: &str) c_GetConversationAttribute -> String);
c_bind!(_get_appbus_event(;;) c_GetAppBusEvent -> String);
c_bind!(_send_profile_event(profile: &str, event_jason: &str;;) c_SendProfileEvent);
c_bind!(_accept_conversation(profile: &str ; conversation_id: i32 ;) c_AcceptConversation);
c_bind!(_block_contact(profile: &str ; conversation_id: i32; ) c_BlockContact);
c_bind!(_unblock_contact(profile: &str ; conversation_id: i32; ) c_UnblockContact);
c_bind!(_get_message(profile: &str; conversation_id: i32, message_index: i32 ;) c_GetMessage -> String);
c_bind!(_get_message_by_id(profile: &str ; conversation_id: i32, message_id: i32 ;) c_GetMessageByID -> String);
c_bind!(_get_message_by_content_hash(profile: &str ; conversation_id: i32 ; hash: &str) c_GetMessagesByContentHash -> String);
c_bind!(_get_messages(profile: &str; conversation_id: i32, message_index: i32, count: i32 ;) c_GetMessages -> String);
c_bind!(_send_message(profile: &str; conversation_id: i32; msg: &str) c_SendMessage -> String);
c_bind!(_send_invitation(profile: &str; conversation_id: i32, target_id: i32;) c_SendInvitation -> String);
c_bind!(_share_file(profile: &str; conversation_id: i32; file_path: &str) c_ShareFile -> String);
c_bind!(_download_file(profile: &str; conversation_id: i32; file_path: &str, manifest_path: &str, file_key: &str) c_DownloadFile);
c_bind!(_check_download_status(profile: &str, file_key: &str;;) c_CheckDownloadStatus);
c_bind!(_verify_or_resume_download(profile: &str; conversation_id: i32; file_key: &str) c_VerifyOrResumeDownload);
c_bind!(_create_group(profile: &str, server: &str, name: &str;;) c_CreateGroup);
c_bind!(_delete_profile(profile: &str, pass: &str;;) c_DeleteProfile);
c_bind!(_archive_conversation(profile: &str; conversation_id: i32;) c_ArchiveConversation);
c_bind!(_delete_contact(profile: &str; conversation_id: i32;) c_DeleteContact);
c_bind!(_import_bundle(profile: &str, bundle: &str;;) c_ImportBundle);
c_bind!(_set_profile_attribute(profile: &str, key: &str, val: &str;;) c_SetProfileAttribute);
c_bind!(_set_conversation_attribute(profile: &str; conversation_id: i32; key: &str, val: &str) c_SetConversationAttribute);
c_bind!(_set_message_attribute(profile: &str; conversation_id: i32, channel_id: i32, message_id: i32; key: &str, val: &str) c_SetMessageAttribute);
c_bind!(_change_password(profile: &str, old_pass: &str, new_pass: &str, new_pass_again: &str;;) c_ChangePassword);
c_bind!(_export_profile(profile: &str, filename: &str;;) c_ExportProfile);
c_bind!(_delete_server(server: &str, current_password: &str;;) c_DeleteServer);
c_bind!(_launch_server(server: &str;;) c_LaunchServer);
c_bind!(_stop_server(server: &str;;) c_StopServer);
c_bind!(_set_server_attribute(server: &str, key: &str, val: &str;;) c_SetServerAttribute);
}
impl CwtchLib for CwtchLibGo {
c_bind!(start_cwtch(app_dir: &str, tor_path: &str;;) c_StartCwtch -> i32);
c_bind!(started(;;) c_Started -> i32);
c_bind!(send_app_event(event_json: &str;;) c_SendAppEvent);
c_bind!(send_profile_event(profile: &str, event_jason: &str;;) c_SendProfileEvent);
c_bind!(get_appbus_event(;;) c_GetAppBusEvent -> String);
fn send_profile_event(&self, profile: &ProfileIdentity, event_jason: &str) {
self._send_profile_event(profile.as_str(), event_jason)
}
c_bind!(create_profile(nick: &str, pass: &str;;) c_CreateProfile);
c_bind!(load_profiles(pass: &str;;) c_LoadProfiles);
c_bind!(accept_conversation(profile: &str ; conversation_id: i32 ;) c_AcceptConversation);
c_bind!(block_contact(profile: &str ; conversation_id: i32; ) c_BlockContact);
c_bind!(unblock_contact(profile: &str ; conversation_id: i32; ) c_UnblockContact);
c_bind!(get_message(profile: &str; conversation_id: i32, message_index: i32 ;) c_GetMessage -> String);
c_bind!(get_message_by_id(profile: &str ; conversation_id: i32, message_id: i32 ;) c_GetMessageByID -> String);
c_bind!(get_message_by_content_hash(profile: &str ; conversation_id: i32 ; hash: &str) c_GetMessagesByContentHash -> String);
c_bind!(get_messages(profile: &str; conversation_id: i32, message_index: i32, count: i32 ;) c_GetMessages -> String);
c_bind!(send_message(profile: &str; conversation_id: i32; msg: &str) c_SendMessage -> String);
c_bind!(send_invitation(profile: &str; conversation_id: i32, target_id: i32;) c_SendInvitation -> String);
c_bind!(share_file(profile: &str; conversation_id: i32; file_path: &str) c_ShareFile -> String);
c_bind!(download_file(profile: &str; conversation_id: i32; file_path: &str, manifest_path: &str, file_key: &str) c_DownloadFile);
c_bind!(check_download_status(profile: &str, file_key: &str;;) c_CheckDownloadStatus);
c_bind!(verify_or_resume_download(profile: &str; conversation_id: i32; file_key: &str) c_VerifyOrResumeDownload);
fn accept_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) {
self._accept_conversation(profile.as_str(), conversation_id.into())
}
fn block_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID) {
self._block_contact(String::from(profile).as_str(), conversation_id.into())
}
fn unblock_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID) {
self._unblock_contact(String::from(profile).as_str(), conversation_id.into())
}
fn get_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32) -> String {
self._get_message(String::from(profile).as_str(), conversation_id.into(), message_index)
}
fn get_message_by_id(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_id: i32) -> String {
self._get_message_by_id(String::from(profile).as_str(), conversation_id.into(), message_id)
}
fn get_message_by_content_hash(&self, profile: &ProfileIdentity, conversation_id: ConversationID, hash: &str) -> String {
self._get_message_by_content_hash(String::from(profile).as_str(), conversation_id.into(), hash)
}
fn get_messages(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32, count: i32) -> String {
self._get_messages(String::from(profile).as_str(), conversation_id.into(), message_index, count)
}
fn send_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, msg: &str) -> String {
self._send_message(String::from(profile).as_str(), conversation_id.into(), msg)
}
fn send_invitation(&self, profile: &ProfileIdentity, conversation_id: ConversationID, target_id: i32) -> String {
self._send_invitation(String::from(profile).as_str(), conversation_id.into(), target_id)
}
fn share_file(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str) -> String {
self._share_file(String::from(profile).as_str(), conversation_id.into(), file_path)
}
fn download_file(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str, manifest_path: &str, file_key: FileKey) {
self._download_file(String::from(profile).as_str(), conversation_id.into(), file_path, manifest_path, String::from(file_key).as_str())
}
fn check_download_status(&self, profile: &ProfileIdentity, file_key: FileKey) {
self._check_download_status(String::from(profile).as_str(), String::from(file_key).as_str())
}
fn verify_or_resume_download(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_key: FileKey) {
self._verify_or_resume_download(String::from(profile).as_str(), conversation_id.into(), String::from(file_key).as_str())
}
fn reset_tor(&self) {
unsafe {
bindings::c_ResetTor();
}
}
c_bind!(create_group(profile: &str, server: &str, name: &str;;) c_CreateGroup);
c_bind!(delete_profile(profile: &str, pass: &str;;) c_DeleteProfile);
c_bind!(archive_conversation(profile: &str; conversation_id: i32;) c_ArchiveConversation);
c_bind!(delete_contact(profile: &str; conversation_id: i32;) c_DeleteContact);
c_bind!(import_bundle(profile: &str, bundle: &str;;) c_ImportBundle);
c_bind!(set_profile_attribute(profile: &str, key: &str, val: &str;;) c_SetProfileAttribute);
fn get_profile_attribute(&self, profile: &str, key: &str) -> Result<Option<String>, CwtchError> {
let resp = self._get_profile_attribute(profile, key);
fn create_group(&self, profile: &ProfileIdentity, server: &str, name: &str) {
self._create_group(String::from(profile).as_str(), server, name)
}
fn delete_profile(&self, profile: &ProfileIdentity, pass: &str) {
self._delete_profile(String::from(profile).as_str(), pass)
}
fn archive_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) {
self._archive_conversation(String::from(profile).as_str(), conversation_id.into())
}
fn delete_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID) {
self._delete_contact(profile.as_str(), conversation_id.into())
}
fn import_bundle(&self, profile: &ProfileIdentity, bundle: &str) {
self._import_bundle(String::from(profile).as_str(), bundle)
}
fn set_profile_attribute(&self, profile: &ProfileIdentity, key: &str, val: &str) {
self._set_profile_attribute(String::from(profile).as_str(), key, val)
}
fn get_profile_attribute(&self, profile: &ProfileIdentity, key: &str) -> Result<Option<String>, CwtchError> {
let resp = self._get_profile_attribute(String::from(profile).as_str(), key);
let attr: Attribute = match serde_json::from_str(&resp) {
Ok(attr) => attr,
Err(e) => return Err(e.to_string()),
@ -131,9 +203,11 @@ impl CwtchLib for CwtchLibGo {
false => Ok(None),
}
}
c_bind!(set_conversation_attribute(profile: &str; conversation_id: i32; key: &str, val: &str) c_SetConversationAttribute);
fn get_conversation_attribute(&self, profile: &str, conversation_id: i32, key: &str) -> Result<Option<String>, CwtchError> {
let resp = self._get_conversation_attribute(profile, conversation_id, key);
fn set_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str, val: &str) {
self._set_conversation_attribute(String::from(profile).as_str(), conversation_id.into(), key, val)
}
fn get_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str) -> Result<Option<String>, CwtchError> {
let resp = self._get_conversation_attribute(String::from(profile).as_str(), conversation_id.into(), key);
let attr: Attribute = match serde_json::from_str(&resp) {
Ok(attr) => attr,
Err(e) => return Err(e.to_string()),
@ -143,11 +217,19 @@ impl CwtchLib for CwtchLibGo {
false => Ok(None),
}
}
c_bind!(set_message_attribute(profile: &str; conversation_id: i32, channel_id: i32, message_id: i32; key: &str, val: &str) c_SetMessageAttribute);
c_bind!(change_password(profile: &str, old_pass: &str, new_pass: &str, new_pass_again: &str;;) c_ChangePassword);
c_bind!(export_profile(profile: &str, filename: &str;;) c_ExportProfile);
fn set_message_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, channel_id: i32, message_id: i32, key: &str, val: &str) {
self._set_message_attribute(String::from(profile).as_str(), conversation_id.into(), channel_id, message_id, key, val)
}
fn change_password(&self, profile: &ProfileIdentity, old_pass: &str, new_pass: &str, new_pass_again: &str) {
self._change_password(String::from(profile).as_str(), old_pass, new_pass, new_pass_again)
}
fn export_profile(&self, profile: &ProfileIdentity, filename: &str) {
self._export_profile(String::from(profile).as_str(), filename)
}
c_bind!(import_profile(filename: &str, password: &str;;) c_ImportProfile -> String);
fn shutdown_cwtch(&self) {
unsafe {
bindings::c_ShutdownCwtch();
@ -156,12 +238,26 @@ impl CwtchLib for CwtchLibGo {
c_bind!(load_servers(password: &str;;) c_LoadServers);
c_bind!(create_server(password: &str, description: &str; autostart: i8;) c_CreateServer);
c_bind!(delete_server(onion: &str, current_password: &str;;) c_DeleteServer);
fn delete_server(&self, server: ServerIdentity, current_password: &str) {
self._delete_server(String::from(server).as_str(), current_password)
}
c_bind!(launch_servers(;;) c_LaunchServers);
c_bind!(launch_server(onion: &str;;) c_LaunchServer);
c_bind!(stop_server(onion: &str;;) c_StopServer);
fn launch_server(&self, server: ServerIdentity) {
self._launch_server(String::from(server).as_str())
}
fn stop_server(&self, server: ServerIdentity) {
self._stop_server(String::from(server).as_str())
}
c_bind!(stop_servers(;;) c_StopServers);
c_bind!(destroy_servers(;;) c_DestroyServers);
c_bind!(set_server_attribute(onion: &str, key: &str, val: &str;;) c_SetServerAttribute);
fn set_server_attribute(&self, server: ServerIdentity, key: &str, val: &str) {
self._set_server_attribute(String::from(server).as_str(), key, val)
}
c_bind!(get_debug_info(;;) c_GetDebugInfo -> String);
fn get_appbus_event(&self) -> Event {
let event_json = self._get_appbus_event();
let cwtch_event: CwtchEvent = serde_json::from_str(&event_json).expect("Error parsing Cwtch event");
Event::from(&cwtch_event)
}
}

822
src/event.rs Normal file
View File

@ -0,0 +1,822 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, FixedOffset};
use chrono::prelude::*;
use std::convert::From;
use crate::structs::{ACL, ConnectionState, CwtchEvent, Message, Profile, Settings};
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
/// Profile ID used to refer to profiles in Cwtch
pub struct ProfileIdentity(String);
impl From<String> for ProfileIdentity {
fn from(x: String) -> Self {
ProfileIdentity(x)
}
}
impl From<ProfileIdentity> for String {
fn from(x: ProfileIdentity) -> Self {
x.into()
}
}
impl From<&str> for ProfileIdentity {
fn from(x: &str) -> Self {
ProfileIdentity(x.to_string())
}
}
impl From<&ProfileIdentity> for String {
fn from(x: &ProfileIdentity) -> Self {
x.0.clone()
}
}
impl ProfileIdentity {
/// Get &str of ProfileIdentity String
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
/// Contact ID used to refer to contacts in Cwtch
pub struct ContactIdentity(String);
impl From<String> for ContactIdentity {
fn from(x: String) -> Self {
ContactIdentity(x)
}
}
impl ContactIdentity {
/// Get &str of ContactIdentity String
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Hash, Eq, PartialEq)]
/// Conversation ID user to refer to a conversation with a Contact or Group in Cwtch
pub struct ConversationID(pub i32) ;
impl From<i32> for ConversationID {
fn from(x: i32) -> Self {
ConversationID(x)
}
}
impl From<ConversationID> for i32 {
fn from(x: ConversationID) -> Self {
x.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
/// Group ID used to refer to a Group in Cwtch
pub struct GroupID(String) ;
impl From<String> for GroupID {
fn from(x: String) -> Self {
GroupID(x)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
/// Server ID user to refer to a server in Cwtch
pub struct ServerIdentity(String);
impl From<String> for ServerIdentity {
fn from(x: String) -> Self {
ServerIdentity(x)
}
}
impl From<&str> for ServerIdentity {
fn from(x: &str) -> Self {
ServerIdentity(x.to_string())
}
}
impl From<ServerIdentity> for String {
fn from(x: ServerIdentity) -> Self {
x.into()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
/// FileKey ID user to refer to a file share in Cwtch
pub struct FileKey(String);
impl From<String> for FileKey {
fn from(x: String) -> Self {
FileKey(x)
}
}
impl From<FileKey> for String {
fn from(x: FileKey) -> Self {
x.into()
}
}
#[derive(Debug, Clone)]
/// Enum for type of notification a UI/client should emit for a new message
pub enum MessageNotification {
/// means no notification
None,
/// means emit a notification that a message event happened only
SimpleEvent,
/// means emit a notification event with Conversation handle included
ContactInfo,
}
impl From<String> for MessageNotification {
fn from(str: String) -> MessageNotification {
match str.as_str() {
"None" => MessageNotification::None,
"SimpleEvent" => MessageNotification::SimpleEvent,
"ContactInfo" => MessageNotification::ContactInfo,
_ => MessageNotification::None,
}
}
}
#[derive(Debug, Clone)]
/// Enum for results from NetworkCheck plugin on profiles
pub enum NetworkCheckStatus {
/// There was an error, this profile cannot connect to itself
Error,
/// Success connecting to self, profile is "online"
Success
}
impl From<String> for NetworkCheckStatus {
fn from(str: String) -> NetworkCheckStatus {
match str.as_str() {
"Error" => NetworkCheckStatus::Error,
"Success" => NetworkCheckStatus::Success,
_ => NetworkCheckStatus::Error,
}
}
}
#[derive(Debug, Clone)]
/// Enum denoting server storage mode
pub enum ServerStorageType {
/// indicates some app supplied default password is being used on this server for storage encryption
DefaultPassword,
/// indicates a user supplied password is being used on this server for storage encryption
Password,
/// indicates no password and no encryption is being used on server storage
NoPassword
}
impl From<String> for ServerStorageType {
fn from(str: String) -> ServerStorageType {
match str.as_str() {
"storage-default-password" => ServerStorageType::DefaultPassword,
"storage-password" => ServerStorageType::Password,
"storage-no-password" => ServerStorageType::NoPassword,
_ => ServerStorageType::DefaultPassword,
}
}
}
#[derive(Debug, Clone)]
/// Enum used by servers to declare their intent to be running or stopped
pub enum ServerIntent {
/// a server intends to be running
Running,
/// a server intends to be stopped
Stopped
}
impl From<String> for ServerIntent {
fn from(str: String) -> ServerIntent {
match str.as_str() {
"running" => ServerIntent::Running,
"stopped" => ServerIntent::Stopped,
_ => ServerIntent::Stopped,
}
}
}
#[derive(Debug, Clone)]
/// Enum for events from Cwtch appbus
pub enum Event {
// App events
/// Emited by libcwtch once Cwtch.Start() has been completed successfully, you can start making API calls, lcg is initialized
CwtchStarted,
/// A new peer has been loaded, details of peer. Identity should be stored so further API calls can be made
NewPeer {
/// identity field
profile_id: ProfileIdentity,
/// optional client specified tag
tag: String,
/// is this a newly created profile event, or a load
created: bool,
/// user supplied name of the profile
name: String,
/// default picture path
default_picture: String,
/// user supplied picture path
picture: String,
/// is the profile online
online: String,
/// The deserialized profile with contacts and server info
profile_data: Result<Profile, String>,
},
/// Cwtch had an error at the app level (not profile level), usually in response to an API call
AppError {
/// details of the app error that occured
error: String
},
/// Global settings being emited from lcg, usually in response to them being sent to be saved by client
UpdateGlobalSettings {
/// map of setting names to values (https://git.openprivacy.ca/cwtch.im/libcwtch-go/src/branch/trunk/utils/settings.go)
settings: Settings,
},
/// A profile has an error, usually emited in response to an API call
PeerError {
/// details of the peer error that occured
error: String
},
/// A profile was successfully deleted, it is no longer usable
PeerDeleted {
/// identity of deleted peer
profile_id: ProfileIdentity
},
/// Cwtch is shutting down, stop making API calls
Shutdown,
/// indicates status of the ACN (tor), usually during boot.
ACNStatus {
/// the percent of ACN boot (-1 to 100)
progress: i8,
/// an associated status message from the ACN
status: String,
},
/// Version of the ACN (currently tor)
ACNVersion {
/// version string from ACN app
version: String,
},
/// Notice from libCwtch that before completing load of a profile a storage migration is occuring so Start will take longer
StartingStorageMiragtion,
/// Notice from libCwtch that the storage migration is complete, profile being loaded resuming
DoneStorageMigration,
// app server events
/// A new server has been loaded
NewServer {
/// identity of server
server: ServerIdentity,
/// sharable / importable server bundle
server_bundle: String,
/// user supplied description
description: String,
/// storage mode the server is using
storage_type: ServerStorageType,
/// does/should the server auto start on cwtch start
autostart: bool,
/// is the server running
running: bool,
},
/// Response to request for server intent change, indicating the server is indending the new state
ServerIntentUpdate {
/// identity of server
server: ServerIdentity,
/// intent of the server to be running or not
intent: ServerIntent,
},
/// Notice a server was deleted (in response to an API call) and is no longer usable
ServerDeleted {
/// identity of server
server: ServerIdentity,
/// was deletion a success
success: bool,
/// optional error string in case of failure to delete
error: Option<String>,
},
/// Stats info for a server, periodically emited
ServerStatsUpdate {
/// identity of server
server: ServerIdentity,
/// count of total messages on the server
total_messages: i32,
/// count of total current connections to the server
connections: i32,
},
// profile events
/// A new message was received
NewMessageFromPeer {
/// identity field
profile_id: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// contact id
contact_id: ContactIdentity,
/// name of contact
nick: String,
/// time message was received
timestamp_received: DateTime<FixedOffset>,
/// the message
message: Message,
/// notification instructions (based on settings)
notification: MessageNotification,
/// path to picture for the contact
picture: String,
},
/// A new contact has been created (imported, added, or contacted by)
ContactCreated {
/// identity field
profile_id: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// contact id
contact_id: ContactIdentity,
/// name of group
nick: String,
/// connection status to group server
status: ConnectionState,
/// number of unread messages in group
unread: i32,
/// path to picture for group
picture: String,
/// path to default picture for group
default_picture: String,
/// total number of messages in group
num_messages: i32,
/// has the user accepted the group
accepted: bool,
/// ACL for the group
access_control_list: ACL,
/// is the group blocked
blocked: bool,
/// is the group syncing
loading: bool,
/// time of last message from the group
last_msg_time: DateTime<FixedOffset>,
},
/// A peer has changed state
PeerStateChange {
/// identity field
profile_id: ProfileIdentity,
/// contact id
contact_id: ContactIdentity,
/// connection status to contact
connection_state: ConnectionState,
},
/// Notice from the network check plugin, a profile self check test by attempting to connecting to itself
NetworkStatus {
/// profile the check was performed on
profile_id: ProfileIdentity,
// it's technically a profile self check
/// error if there was one (can be empty)
error: String,
/// status of profile self connection check
status: NetworkCheckStatus,
},
/// Information from the ACN about a peer
ACNInfo {
/// identity field
profile_id: ProfileIdentity,
/// contact id
contact_id: ContactIdentity,
/// key of info
key: String,
/// data of info
data: String,
},
/// a profile attribute has been updated with a new value
UpdatedProfileAttribute {
/// identity field
profile_id: ProfileIdentity,
/// attribute key
key: String,
/// attribute new value
value: String,
},
/// emited to confirm ack of a message succeeded
IndexedAcknowledgement {
/// identity field
profile_id: ProfileIdentity,
/// conversation id
conversation_id: i32,
/// index of message acked
index: i32,
},
/// emited to signal failure to ack a message
IndexedFailure {
/// identity field
profile_id: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// index of failure of message to ack
index: i32,
/// contact id
contact_id: ContactIdentity,
/// error string
error: String,
},
/// a peer has acked a message
PeerAcknowledgement {
/// identity field
profile_id: ProfileIdentity,
/// message id this is an ack to
event_id: String,
/// contact id
contact_id: ContactIdentity,
/// conversation id
conversation_id: i32,
},
/// New message received on a group
NewMessageFromGroup {
/// identity field
profile_id: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// time of message
timestamp_sent: DateTime<FixedOffset>,
/// contact id
contact_id: ContactIdentity,
/// message index
index: i32,
/// the message
message: Message,
/// hash of the message
content_hash: String,
/// path to picture for sender
picture: String,
/// name of sender
nick: String,
/// notification policy (based on settings)
notification: MessageNotification,
},
/// notice a group has been created
GroupCreated {
/// identity field
profile_id: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// group id
group_id: GroupID,
/// server the group is on
group_server: String,
/// name of group
group_name: String,
/// path to picture for group
picture: String,
/// Access Control List for group
access_control_list: ACL,
},
/// notice a new group exists
NewGroup {
/// identity field
profile_id: ProfileIdentity,
/// conversation id
conversation_id: ConversationID,
/// group id
group_id: GroupID,
/// server the group is on
group_server: String,
/// invite string
group_invite: String,
/// group name
group_name: String,
/// path to group picture
picture: String,
/// Access Control List for group
access_control_list: ACL,
},
/// a server connection state has changed
ServerStateChange {
/// identity field
profile_id: ProfileIdentity,
/// server the group is on
group_server: String,
/// state of connection to server
state: ConnectionState,
},
/// A getval call to a peer has returned a response
NewRetValMessageFromPeer {
/// identity field
profile_id: ProfileIdentity,
/// conversation id
contact_id: ContactIdentity,
/// scope of the val
scope: String,
/// path of the val (zone.key)
path: String,
/// does the queried for value exist
exists: bool,
/// value
data: String,
/// optional filepath if there was a downloaded component
file_path: Option<String>,
},
/// result of a call to share a file, the filekey and manifest to operate on it
ShareManifest {
/// identity field
profile_id: ProfileIdentity,
/// filekey
filekey: FileKey,
/// serialized manifest of the share
serialized_manifest: String,
},
/// Information on a peer fileshare has been received
ManifestSizeReceived {
/// identity field
profile_id: ProfileIdentity,
/// filekey
filekey: FileKey,
/// size of manifest received for a share
manifest_size: i32,
/// contact id
contact_id: ContactIdentity,
},
/// An error has occured while trying to parse a peer sharefile
ManifestError {
/// identity field
profile_id: ProfileIdentity,
/// contact id
contact_id: ContactIdentity,
/// filekey
filekey: FileKey,
},
/// A peer message about a shared file has been received
ManifestReceived {
/// identity field
profile_id: ProfileIdentity,
/// contact id
contact_id: ContactIdentity,
/// filekey
filekey: FileKey,
/// serialized manifest
serialized_manifest: String,
},
/// a received manfiest has been saved
ManifestSaved {
/// identity field
profile_id: ProfileIdentity,
/// contact id
contact_id: ContactIdentity,
/// filekey
filekey: FileKey,
/// serialized manifest
serialized_manifest: String,
/// temporary storage path for share download
temp_file: String,
/// contact suggested share file name
name_suggestion: String,
},
/// periodically emited status updates about an active download of a shared file
FileDownloadProgressUpdate {
/// identity field
profile_id: ProfileIdentity,
/// filekey
filekey: FileKey,
/// progress of download of share in chunks
progress: i32,
/// size of share in chunks
filesize_in_chunks: i32,
/// contact suggested name for file
name_suggestion: String,
},
// FileDownloaded, ??
/// Indicates an event was sent from libCwtch that we don't handle/didn't anticipate
/// still passing it on giving the user a chance to react, but should be reported so we can handle it properly
ErrUnhandled {
/// name of unhandled event
name: String,
/// map of key:val attributes of unhandled event
data: HashMap<String, String>,
},
}
impl From<&CwtchEvent> for Event {
fn from(cwtch_event: &CwtchEvent) -> Self {
println!("EVENT: {:?}", cwtch_event);
match cwtch_event.event_type.as_str() {
"CwtchStarted" => Event::CwtchStarted,
"NewPeer" => Event::NewPeer {
profile_id: cwtch_event.data["Identity"].clone().into(),
tag: cwtch_event.data["tag"].clone(),
created: cwtch_event.data["Created"] == "true",
name: cwtch_event.data["name"].clone(),
default_picture: cwtch_event.data["defaultPicture"].clone(),
picture: cwtch_event.data["picture"].clone(),
online: cwtch_event.data["Online"].clone(),
profile_data: Profile::new(
cwtch_event.data["Identity"].clone().into(),
&cwtch_event.data["name"],
&cwtch_event.data["picture"],
&cwtch_event.data["ContactsJson"],
&cwtch_event.data["ServerList"],
)
},
"NewMessageFromPeer" => Event::NewMessageFromPeer {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(),
contact_id: cwtch_event.data["RemotePeer"].clone().into(),
nick: cwtch_event.data["Nick"].clone(),
timestamp_received: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampReceived"].as_str()).unwrap_or( DateTime::from(Utc::now())),
message: Message::from_json(&cwtch_event.data["Data"]),
notification: MessageNotification::from(cwtch_event.data["notification"].clone()),
picture: cwtch_event.data["picture"].clone(),
},
"PeerError" => Event::PeerError { error: cwtch_event.data["Error"].clone() },
"AppError" => Event::AppError {
error: cwtch_event.data["Error"].clone(),
},
"ContactCreated" => Event::ContactCreated {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].clone().parse().unwrap_or(-2).into(),
contact_id: cwtch_event.data["RemotePeer"].clone().into(),
unread: cwtch_event.data["unread"].clone().parse().unwrap_or(0),
picture: cwtch_event.data["picture"].clone(),
default_picture: cwtch_event.data["defaultPicture"].clone(),
num_messages: cwtch_event.data["numMessages"].clone().parse().unwrap_or(0),
nick: cwtch_event.data["nick"].clone(),
accepted: cwtch_event.data["accepted"].clone().parse().unwrap_or(false),
status: ConnectionState::from(cwtch_event.data["status"].as_str()),
access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()),
blocked: cwtch_event.data["blocked"].clone().parse().unwrap_or(false),
loading: cwtch_event.data["loading"].clone().parse().unwrap_or(false),
last_msg_time: DateTime::parse_from_rfc3339(cwtch_event.data["lastMsgTime"].as_str()).unwrap_or(DateTime::from(Utc::now())),
},
"PeerStateChange" => Event::PeerStateChange {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
contact_id: cwtch_event.data["RemotePeer"].clone().into(),
connection_state: ConnectionState::from(cwtch_event.data["ConnectionState"].as_str()),
},
"UpdateGlobalSettings" => Event::UpdateGlobalSettings {
settings: serde_json::from_str(cwtch_event.data["Data"].as_str()).expect("could not parse settings from libCwtch-go"),
},
"PeerDeleted" => Event::PeerDeleted { profile_id: cwtch_event.data["Identity"].clone().into() },
"ACNStatus" => Event::ACNStatus {
progress: cwtch_event.data["Progress"].parse().unwrap_or(0),
status: cwtch_event.data["Status"].clone(),
},
"ACNVersion" => Event::ACNVersion { version: cwtch_event.data["Data"].clone()},
"NetworkError" => Event::NetworkStatus {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
error: cwtch_event.data["Error"].clone(),
status: NetworkCheckStatus::from(cwtch_event.data["Status"].clone())
},
"ACNInfo" => Event::ACNInfo {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
contact_id: cwtch_event.data["Handle"].clone().into(),
key: cwtch_event.data["Key"].clone(),
data: cwtch_event.data["Data"].clone(),
},
"UpdatedProfileAttribute" => Event::UpdatedProfileAttribute {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
key: cwtch_event.data["Key"].clone(),
value: cwtch_event.data["Data"].clone(),
},
"IndexedAcknowledgement" => Event::IndexedAcknowledgement {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-1).into(),
index: cwtch_event.data["Index"].parse().unwrap_or(-1),
},
"ShareManifest" => Event::ShareManifest {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
serialized_manifest: cwtch_event.data["SerializedManifest"].clone(),
},
"NewServer" => Event::NewServer {
server: cwtch_event.data["Onion"].clone().into(),
server_bundle: cwtch_event.data["ServerBundle"].clone(),
description: cwtch_event.data["Description"].clone(),
storage_type: ServerStorageType::from(cwtch_event.data["StorageType"].clone()),
autostart: cwtch_event.data["Autostart"].parse().unwrap_or(false),
running: cwtch_event.data["Running"].parse().unwrap_or(false),
},
"ServerIntentUpdate" => Event::ServerIntentUpdate {
server: cwtch_event.data["Identity"].clone().into(),
intent: ServerIntent::from(cwtch_event.data["Intent"].clone())
},
"ServerDeleted" => Event::ServerDeleted {
server: cwtch_event.data["Identity"].clone().into(),
success: cwtch_event.data["Status"].clone() == "success",
error: match cwtch_event.data.get("Error") {
Some(e) => Some(e.clone()),
None => None,
}
},
"ServerStatsUpdate" => Event::ServerStatsUpdate {
server: cwtch_event.data["Identity"].clone().into(),
total_messages: cwtch_event.data["TotalMessages"].parse().unwrap_or(0),
connections: cwtch_event.data["Connections"].parse().unwrap_or(0),
},
"ManifestSizeReceived" => Event::ManifestSizeReceived {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
manifest_size: cwtch_event.data["ManifestSize"].parse().unwrap_or(0),
contact_id: cwtch_event.data["Handle"].clone().into(),
},
"ManifestError" => Event::ManifestError {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
contact_id: cwtch_event.data["Handle"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
},
"ManifestReceived" => Event::ManifestReceived {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
contact_id: cwtch_event.data["Handle"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
serialized_manifest: cwtch_event.data["SerializedManifest"].clone(),
},
"ManifestSaved" => Event::ManifestSaved {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
contact_id: cwtch_event.data["Handle"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
serialized_manifest: cwtch_event.data["SerializedManifest"].clone(),
temp_file: cwtch_event.data["TempFile"].clone(),
name_suggestion: cwtch_event.data["NameSuggestion"].clone(),
},
"FileDownloadProgressUpdate" => Event::FileDownloadProgressUpdate {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
filekey: cwtch_event.data["FileKey"].clone().into(),
progress: cwtch_event.data["Progress"].parse().unwrap_or(0),
filesize_in_chunks: cwtch_event.data["FileSizeInChunks"].parse().unwrap_or(0),
name_suggestion: cwtch_event.data["NameSuggestion"].clone(),
},
"PeerAcknowledgement" => Event::PeerAcknowledgement {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
event_id: cwtch_event.data["EventID"].clone(),
contact_id: cwtch_event.data["RemotePeer"].clone().into(),
conversation_id: cwtch_event.data.get("ConversationID").unwrap_or(&"0".to_string()).parse().unwrap_or(0)
},
"NewMessageFromGroup" => Event::NewMessageFromGroup {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
timestamp_sent: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampSent"].as_str()).unwrap_or( DateTime::from(Utc::now())),
index: cwtch_event.data["RemotePeer"].parse().unwrap_or(-1),
content_hash: cwtch_event.data["ContentHash"].clone(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(),
contact_id: cwtch_event.data["RemotePeer"].clone().into(),
nick: cwtch_event.data["Nick"].clone(),
message: Message::from_json(&cwtch_event.data["Data"]),
notification: MessageNotification::from(cwtch_event.data["notification"].clone()),
picture: cwtch_event.data["picture"].clone(),
},
"GroupCreated" => Event::GroupCreated {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(),
group_id: cwtch_event.data["GroupID"].clone().into(),
group_server: cwtch_event.data["GroupServer"].clone(),
group_name: cwtch_event.data["GroupName"].clone(),
picture: cwtch_event.data["picture"].clone(),
access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()),
},
"NewGroup" => Event::NewGroup {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(),
group_id: cwtch_event.data["GroupID"].clone().into(),
group_server: cwtch_event.data["GroupServer"].clone(),
group_invite: cwtch_event.data["GroupInvite"].clone(),
group_name: cwtch_event.data["GroupName"].clone(),
picture: cwtch_event.data["picture"].clone(),
access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()),
},
"ServerStateChange" => Event::ServerStateChange {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
group_server: cwtch_event.data["GroupServer"].clone(),
state: ConnectionState::from(cwtch_event.data["ConnectionState"].as_str()),
},
"NewRetValMessageFromPeer" => Event::NewRetValMessageFromPeer {
profile_id: cwtch_event.data["ProfileOnion"].clone().into(),
contact_id: cwtch_event.data["RemotePeer"].clone().into(),
scope: cwtch_event.data["Scope"].clone(),
path: cwtch_event.data["Path"].clone(),
exists: cwtch_event.data["Exists"].parse().unwrap_or(false),
data: cwtch_event.data["Data"].clone(),
file_path: match cwtch_event.data.get("FilePath") {
Some(fp) => Some(fp.to_string()),
None => None,
},
},
_ => Event::ErrUnhandled {
name: cwtch_event.event_type.to_string(),
data: cwtch_event.data.clone(),
},
}
}
}

View File

@ -3,11 +3,15 @@
#![doc(html_root_url = "https://git.openprivacy.ca/cwtch.im/libcwtch-rs")]
#![deny(missing_docs)]
use crate::event::{ConversationID, Event, FileKey, ProfileIdentity, ServerIdentity};
mod bindings_go;
mod cwtchlib_go;
/// Basic structs using data from Cwtch and for deserializing JSON and serializing to JSON to communicate with Cwtch
pub mod structs;
/// Additional structs for advnaced event handling and converstion helpers
pub mod event;
/// Error type for Cwtch lib related errors, intended for use with Result
pub type CwtchError = String;
@ -24,10 +28,10 @@ pub trait CwtchLib {
fn send_app_event(&self, event_json: &str);
/// Send json of a structs::CwtchEvent to a cwtch profile
fn send_profile_event(&self, profile: &str, event_json: &str);
fn send_profile_event(&self, profile: &ProfileIdentity, event_json: &str);
/// Pull json of a structs::CwtchEvent off the appbus for responding to
fn get_appbus_event(&self) -> String;
fn get_appbus_event(&self) -> Event;
/// Create a new profile encrypted with pass
fn create_profile(&self, nick: &str, pass: &str);
@ -36,88 +40,88 @@ pub trait CwtchLib {
fn load_profiles(&self, pass: &str);
/// Cause profile to accept conversation
fn accept_conversation(&self, profile: &str, conversation_id: i32);
fn accept_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID);
/// Cause profile to block conversation
fn block_contact(&self, profile: &str, conversation_id: i32);
fn block_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID);
/// Cause profile to unblock contact
fn unblock_contact(&self, profile: &str, conversation_id: i32);
fn unblock_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID);
/// Get a specific message for conversation of profile by index
fn get_message(&self, profile: &str, conversation_id: i32, message_index: i32) -> String;
fn get_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32) -> String;
/// Get a specific message for a conversation by its id
fn get_message_by_id(&self, profile: &str, conversation_id: i32, message_id: i32) -> String;
fn get_message_by_id(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_id: i32) -> String;
/// Get a specific message for conversation of profile by hash
fn get_message_by_content_hash(
&self,
profile: &str,
conversation_id: i32,
profile: &ProfileIdentity,
conversation_id: ConversationID,
hash: &str,
) -> String;
/// Bulk get messages starting at message index and of count amoung
fn get_messages(&self, profile: &str, conversation_id: i32, message_index: i32, count: i32) -> String;
fn get_messages(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32, count: i32) -> String;
/// Send json of a structs::Message from profile to contact. Returns computed sent message (including index and hash values)
fn send_message(&self, profile: &str, conversation_id: i32, msg: &str) -> String;
fn send_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, msg: &str) -> String;
/// Send profile's contact an invite for/to target. Returns computed sent message (including index and hash values)
fn send_invitation(&self, profile: &str, conversation_id: i32, target_id: i32) -> String;
fn send_invitation(&self, profile: &ProfileIdentity, conversation_id: ConversationID, target_id: i32) -> String;
/// share a file file_path with a conersation. Returns computed sent message (including index and hash values)
fn share_file(&self, profile: &str, conversation_id: i32, file_path: &str) -> String;
fn share_file(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str) -> String;
/// download a file from a conversation to the file_path
fn download_file(
&self,
profile: &str,
conversation_id: i32,
profile: &ProfileIdentity,
conversation_id: ConversationID,
file_path: &str,
manifest_path: &str,
file_key: &str,
file_key: FileKey,
);
/// Query the status of a download
fn check_download_status(&self, profile: &str, file_key: &str);
fn check_download_status(&self, profile: &ProfileIdentity, file_key: FileKey);
/// Verufy a download is done, and if not, resume it
fn verify_or_resume_download(&self, profile: &str, conversation_id: i32, file_key: &str);
fn verify_or_resume_download(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_key: FileKey);
/// Ask the ACN inside the Cwtch app to restart the tor connection
fn reset_tor(&self);
/// Cause profile to create a group on server with name
fn create_group(&self, profile: &str, server: &str, name: &str);
fn create_group(&self, profile: &ProfileIdentity, server: &str, name: &str);
/// Delete profile with encryption/password check of pass
fn delete_profile(&self, profile: &str, pass: &str);
fn delete_profile(&self, profile: &ProfileIdentity, pass: &str);
/// Cause profile to archive conversation with contact
fn archive_conversation(&self, profile: &str, conversation_id: i32);
fn archive_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID);
/// Cause profile to delete contact/group identified by handle
fn delete_contact(&self, profile: &str, conversation_id: i32);
fn delete_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID);
/// Cuase profile to attempt to import a contact/group/keybundle identified by bundle
fn import_bundle(&self, profile: &str, bundle: &str);
fn import_bundle(&self, profile: &ProfileIdentity, bundle: &str);
/// Set a profile attribute key to val
fn set_profile_attribute(&self, profile: &str, key: &str, val: &str);
fn set_profile_attribute(&self, profile: &ProfileIdentity, key: &str, val: &str);
/// Get a profile attribute
fn get_profile_attribute(&self, profile: &str, key: &str) -> Result<Option<String>, CwtchError>;
fn get_profile_attribute(&self, profile: &ProfileIdentity, key: &str) -> Result<Option<String>, CwtchError>;
/// Set a profile's contact's attribute of key to val
fn set_conversation_attribute(&self, profile: &str, conversation_id: i32, key: &str, val: &str);
fn set_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str, val: &str);
/// Set an attribute on a message in a conversation
fn set_message_attribute(
&self,
profile: &str,
conversation_id: i32,
profile: &ProfileIdentity,
conversation_id: ConversationID,
channel_id: i32,
message_id: i32,
attribute_key: &str,
@ -125,13 +129,13 @@ pub trait CwtchLib {
);
/// Get an attribute for a conversation
fn get_conversation_attribute(&self, profile: &str, conversation_id: i32, key: &str) -> Result<Option<String>, CwtchError>;
fn get_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str) -> Result<Option<String>, CwtchError>;
/// Change a profile's password to new_pass if old_pass is correct
fn change_password(&self, profile: &str, old_pass: &str, new_pass: &str, new_pass_again: &str);
fn change_password(&self, profile: &ProfileIdentity, old_pass: &str, new_pass: &str, new_pass_again: &str);
/// Export a profile to filename
fn export_profile(&self, profile: &str, filename: &str);
fn export_profile(&self, profile: &ProfileIdentity, filename: &str);
/// Import a profile from a file with supplied password. Json of a profile struct returned on success
fn import_profile(&self, filename: &str, password: &str) -> String;
@ -148,16 +152,16 @@ pub trait CwtchLib {
fn create_server(&self, password: &str, description: &str, autostart: i8);
/// Delete the specified server (if password is correct)
fn delete_server(&self, onion: &str, current_password: &str);
fn delete_server(&self, server: ServerIdentity, current_password: &str);
/// Launch all loaded servers
fn launch_servers(&self);
/// Launch the specified server
fn launch_server(&self, onion: &str);
fn launch_server(&self, server: ServerIdentity);
/// Stop the specified server
fn stop_server(&self, onion: &str);
fn stop_server(&self, server: ServerIdentity);
/// Stop all running servers
fn stop_servers(&self);
@ -166,7 +170,7 @@ pub trait CwtchLib {
fn destroy_servers(&self);
/// Set the specified server's attribute of key to val
fn set_server_attribute(&self, onion: &str, key: &str, val: &str);
fn set_server_attribute(&self, server: ServerIdentity, key: &str, val: &str);
/// Get debug info (mem, goroutine stats) from lcg in json
fn get_debug_info(&self) -> String;

View File

@ -1,10 +1,12 @@
use crate::structs::ConnectionState::Disconnected;
use crate::CwtchLib;
use crate::{ConversationID, CwtchLib, ProfileIdentity};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DefaultOnError};
use serde_repr::*;
use std::collections::HashMap;
use crate::event::ContactIdentity;
#[derive(Serialize, Deserialize, Debug)]
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
/// Defines the states a Cwtch connection can be in
pub enum ConnectionState {
/// The Cwtch connection is not conected at all
@ -29,9 +31,9 @@ impl Default for ConnectionState {
}
}
impl ConnectionState {
impl From<&str> for ConnectionState {
/// Creates a ConnectionState from a string sent from libcwtch-go
pub fn new(name: &str) -> Self {
fn from(name: &str) -> Self {
match name {
"Disconnected" => ConnectionState::Disconnected,
"Connecting" => ConnectionState::Connecting,
@ -81,7 +83,7 @@ pub struct CwtchEvent {
pub data: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
#[allow(non_snake_case)]
/// AccessControl is a type determining client assigned authorization to a peer
@ -98,15 +100,15 @@ pub struct AccessControl {
pub type ACL = HashMap<String, AccessControl>;
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
/// Struct to serialize/deserialize conversations coming from libcwtch-go
pub struct Conversation {
/// onion address / id of the conversation
#[serde(alias = "onion")]
pub handle: String,
pub contact_id: ContactIdentity,
/// unique identifier of the contact/conversation to be used in API access
pub identifier: i32,
pub identifier: ConversationID, // TODO TEST does this work here for serde deserializtion
/// display name of the conversation, as determined in libcwtch-go from name specified by contact
pub name: String,
#[serde_as(deserialize_as = "DefaultOnError")]
@ -125,7 +127,7 @@ pub struct Conversation {
//attr: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// Struct to serialize/deserialize servers coming from libcwtch-go
pub struct Server {
/// onion address of the server
@ -134,13 +136,11 @@ pub struct Server {
pub status: ConnectionState,
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Clone)]
/// Struct to serialize/deserialize profiles coming from libcwtch-go
pub struct Profile {
/// onion address / ID of the profile
#[serde(alias = "onion")]
pub handle: String,
pub profile_id: ProfileIdentity,
/// nick name of the onion as supplied by libcwtch-go based on "name" attribute
pub nick: String,
/// path to a profile image, controled by "picture" attribute
@ -148,22 +148,66 @@ pub struct Profile {
/// all profile attributes
pub attr: HashMap<String, String>,
/// map of conversation [ onion => conversation ]
pub conversations: HashMap<i32, Conversation>,
pub conversations: HashMap<ConversationID, Conversation>,
/// map of servers [ onion => server ]
pub servers: HashMap<String, Server>,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize_repr, Deserialize_repr, PartialEq, Clone)]
#[repr(i32)]
/// Enum matching message types and their overlays as defined in Cwtch
/// - https://git.openprivacy.ca/cwtch.im/cwtch/src/commit/907a7ca638fbcbaac5d652608d455d4217597fa9/model/overlay.go
/// - https://git.openprivacy.ca/cwtch.im/cwtch-ui/src/commit/3a752b73972cbfc53b6da26116fe018ee2ac2343/lib/models/message.dart
pub enum MessageType {
/// A standard Cwtch message of text
TextMessage = 1,
/// This message is a text message but it also contains a reference to the message it is quoting
QuotedMessage = 10,
/// A shared contact message
SuggestContact = 100,
/// A group invite message
InviteGroup = 101,
/// a share file message
FileShare = 200,
/// This message is of an unsupported type so malformed
MalformedMessage,
}
impl From<i32> for MessageType {
fn from(x: i32) -> Self {
match x {
1 => MessageType::TextMessage,
10 => MessageType::QuotedMessage,
100 => MessageType::SuggestContact,
101 => MessageType::InviteGroup,
200 => MessageType::FileShare,
_ => MessageType::MalformedMessage,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
/// Struct to serialize/deserialize messages sent over Cwtch between profiles / conversation
pub struct Message {
/// overlay id that the message is targeting as defined in cwtch/model/overlay.go
/// [ OverlayChat = 1, OverlayInviteContact = 100, OverlayInviteGroup = 101, OverlayFileSharing = 200 ]
pub o: i64,
pub o: MessageType,
/// data of the message
pub d: String,
}
#[derive(Debug, Serialize, Deserialize)]
impl Message {
/// parse json into a Message
pub fn from_json(json: &str) -> Self {
match serde_json::from_str(json) {
Ok(m) => m,
Err(e) => Message{o: MessageType::MalformedMessage, d: e.to_string()}
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
/// Settings as defined in libcwtch-go/utils/settings.go and should be populated by handeling the UpdateGlobalSettings event emited during cwtch.start()
#[allow(non_snake_case)]
pub struct Settings {
@ -265,7 +309,7 @@ impl Profile {
/// contacts_json as supplied by libcwtch-go, a map of conversations
/// server_list as supplied by libcwtch-go, a map of servers
pub fn new(
identity: &str,
identity: ProfileIdentity,
name: &str,
picture: &str,
conversations_json: &str,
@ -280,7 +324,7 @@ impl Profile {
Err(e) => return Err(e),
};
Ok(Profile {
handle: identity.to_string(),
profile_id: identity,
nick: name.to_string(),
image_path: picture.to_string(),
attr: Default::default(),
@ -289,8 +333,8 @@ impl Profile {
})
}
fn process_conversations(conversations_json: &str) -> Result<HashMap<i32, Conversation>, String> {
let mut conversations: HashMap<i32, Conversation> = HashMap::new();
fn process_conversations(conversations_json: &str) -> Result<HashMap<ConversationID, Conversation>, String> {
let mut conversations: HashMap<ConversationID, Conversation> = HashMap::new();
if conversations_json == "null" {
return Ok(conversations);
}
@ -320,8 +364,8 @@ impl Profile {
}
/// Find a conversation_id associated with a remote handle (used for some events with no conversation id like PeerStateChange)
pub fn find_conversation_id_by_handle(&self, handle: String) -> Option<i32> {
match self.conversations.values().filter(|c| c.handle == handle).next() {
pub fn find_conversation_id_by_handle(&self, contact_id: ContactIdentity) -> Option<ConversationID> {
match self.conversations.values().filter(|c| c.contact_id == contact_id).next() {
Some(conversation) => Some(conversation.identifier),
None => None
}