├── src ├── constants │ ├── mod.rs │ └── protocol.rs ├── events │ ├── mod.rs │ └── model.rs ├── ui │ ├── mod.rs │ ├── chat_window.ui │ ├── chat_window.rs │ └── main_win.rs ├── models │ ├── mod.rs │ ├── message.rs │ ├── event.rs │ └── model.rs ├── core │ ├── mod.rs │ ├── download.rs │ └── fileserver.rs ├── main.rs └── util.rs ├── .gitignore ├── resources ├── eye.png ├── file.png └── menu.png ├── screenshots ├── 截图.png ├── 用户列表.png └── 聊天窗口.png ├── README.md ├── Cargo.toml └── Cargo.lock /src/constants/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod protocol; 2 | -------------------------------------------------------------------------------- /src/events/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod model; 3 | -------------------------------------------------------------------------------- /src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod chat_window; 2 | pub mod main_win; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | .gitignore 4 | .idea/ 5 | .DS_Store -------------------------------------------------------------------------------- /src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod event; 2 | pub mod model; 3 | pub mod message; 4 | -------------------------------------------------------------------------------- /resources/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langzime/ipmsg-rs/HEAD/resources/eye.png -------------------------------------------------------------------------------- /resources/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langzime/ipmsg-rs/HEAD/resources/file.png -------------------------------------------------------------------------------- /resources/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langzime/ipmsg-rs/HEAD/resources/menu.png -------------------------------------------------------------------------------- /screenshots/截图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langzime/ipmsg-rs/HEAD/screenshots/截图.png -------------------------------------------------------------------------------- /screenshots/用户列表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langzime/ipmsg-rs/HEAD/screenshots/用户列表.png -------------------------------------------------------------------------------- /screenshots/聊天窗口.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langzime/ipmsg-rs/HEAD/screenshots/聊天窗口.png -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod download; 2 | pub mod fileserver; 3 | 4 | 5 | use crossbeam_channel::unbounded; 6 | use once_cell::sync::Lazy; 7 | use crate::models::event::ModelEvent; 8 | 9 | 10 | /// 11 | /// 全局队列 12 | pub static GLOBAL_CHANNEL: Lazy<(crossbeam_channel::Sender, crossbeam_channel::Receiver)> = Lazy::new(|| { 13 | let (model_sender, model_receiver): (crossbeam_channel::Sender, crossbeam_channel::Receiver) = unbounded(); 14 | return (model_sender, model_receiver); 15 | }); 16 | 17 | /// 18 | /// 全局队列发送 19 | pub static GLOBLE_SENDER: Lazy> = Lazy::new(|| { 20 | return GLOBAL_CHANNEL.0.clone(); 21 | }); 22 | 23 | /// 24 | /// 全局队列接收 25 | pub static GLOBLE_RECEIVER: Lazy> = Lazy::new(|| { 26 | return GLOBAL_CHANNEL.1.clone(); 27 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust版飞鸽传书 2 | 3 | > 基于 GTK4 已在mac big sur下测试 4 | 5 | 6 | ## 已实现功能 7 | - [x] 聊天 8 | - [x] 发送文件 9 | - [x] 接收文件 10 | 11 | ## 跨平台支持 12 | 13 | > 没有整理成linux的项目格式,不然就只支持linux了。\ 14 | > 需要的可以安装运行环境,自己编译,按道理来说应该windows、mac、linux都支持的 15 | 16 | ### windows 17 | 18 | > 安装gtk4+libadwaita \ 19 | > 官方教程 \ 20 | > https://gtk-rs.org/gtk4-rs/stable/latest/book/installation_windows.html 21 | > \ 22 | > https://gtk-rs.org/gtk4-rs/stable/latest/book/libadwaita.html 23 | > 24 | > 建议用MSYS2安装gtk和libadwaita可以省很多事儿 25 | 26 | ### macos 27 | 28 | > 安装gtk4+libadwaita \ 29 | > brew install gtk4 libadwaita 30 | 31 | ### linux 32 | 33 | > 安装gtk4+libadwaita \ 34 | > 官方教程 \ 35 | > https://gtk-rs.org/gtk4-rs/stable/latest/book/installation_linux.html 36 | > \ 37 | > https://gtk-rs.org/gtk4-rs/stable/latest/book/libadwaita.html 38 | > 39 | ## 截图 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![windows_subsystem = "windows"] 2 | 3 | use human_panic::setup_panic; 4 | use gio::prelude::*; 5 | use log::info; 6 | use crate::ui::main_win::MainWindow; 7 | 8 | mod models; 9 | mod util; 10 | mod events; 11 | mod ui; 12 | mod core; 13 | mod constants; 14 | 15 | const APP_ID: &'static str = "com.github.ipmsg-rs"; 16 | 17 | fn main() -> glib::ExitCode { 18 | setup_panic!(); 19 | std::env::set_var("RUST_LOG", "info"); 20 | env_logger::init(); 21 | let application = adw::Application::builder().application_id(APP_ID).build(); 22 | application.connect_startup(move |app| { 23 | info!("starting up"); 24 | MainWindow::new(app); 25 | }); 26 | application.connect_activate(|_| { 27 | info!("connect_activate"); 28 | }); 29 | 30 | application.connect_shutdown(move |_| { 31 | info!("shutdown!"); 32 | }); 33 | 34 | application.run() 35 | } -------------------------------------------------------------------------------- /src/models/message.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | use log::info; 3 | use crate::models::model::{self, Packet}; 4 | use crate::constants::protocol::{IPMSG_SENDMSG, IPMSG_FILEATTACHOPT}; 5 | 6 | pub fn create_sendmsg(context :String, files: Vec, tar_ip: String) -> (Packet, Option){ 7 | let commond = if files.len() > 0 { IPMSG_SENDMSG|IPMSG_FILEATTACHOPT } else { IPMSG_SENDMSG };//如果有文件,需要扩展文件 8 | let share_info = if files.len() > 0 { 9 | Some(model::ShareInfo { 10 | packet_no: Local::now().timestamp() as u32, 11 | host: tar_ip.clone(), 12 | host_cnt: 1, 13 | file_info: files.clone(), 14 | file_cnt: 1, 15 | attach_time: Local::now().time(), 16 | }) 17 | }else { 18 | None 19 | }; 20 | 21 | let mut additional = String::new(); 22 | for (i, file) in files.iter().enumerate() { 23 | additional.push_str(file.to_fileinfo_msg().as_str()); 24 | additional.push('\u{7}'); 25 | } 26 | let mut context1: String = context.to_owned(); 27 | context1.push('\u{0}'); 28 | context1.push_str(additional.as_str()); 29 | context1.push('\u{0}'); 30 | let packet = Packet::new(commond, Some(context1)); 31 | info!("send message {:?}", packet); 32 | return (packet, share_info); 33 | } 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ipmsg-rs" 3 | version = "0.7.1" 4 | authors = ["wangyanqing@langzi.me"] 5 | description = "ipmsg rewrite by rust." 6 | repository = "https://github.com/langzime/raudient" 7 | documentation = "https://docs.rs/raudient" 8 | keywords = ["ipmsg", "ipmessager", "ipmsg-rs"] 9 | license = "MIT" 10 | edition = "2021" 11 | 12 | [[bin]] 13 | name = "ipmsg-rs" 14 | path = "src/main.rs" 15 | 16 | [dependencies] 17 | chrono = "0.4" 18 | hostname = "0.3" 19 | encoding = "0.2" 20 | #lazy_static = "1" 21 | log = "0.4" 22 | env_logger = "0.10" 23 | anyhow = "1" 24 | thiserror = "1.0" 25 | crossbeam-channel = "0.5" 26 | combine = "4" 27 | once_cell="1.19" 28 | gdk-pixbuf = "0.18" 29 | human-panic = "1.1.0" 30 | local-ip-address = "0.5" 31 | 32 | async-channel = "2.1" 33 | 34 | adw = { version = "0.5", package = "libadwaita", features = ["v1_4"] } 35 | 36 | [dependencies.gtk] 37 | version = "0.7" 38 | package = "gtk4" 39 | features = ["v4_8"] 40 | 41 | [dependencies.gdk] 42 | version = "0.7" 43 | package = "gdk4" 44 | 45 | [dependencies.gio] 46 | version = "0.18" 47 | features = ["v2_70"] 48 | 49 | [dependencies.glib] 50 | version = "0.18" 51 | features = ["v2_70"] 52 | 53 | #[dependencies.gettext-rs] 54 | #version = "0.7" 55 | #features = ["gettext-system"] 56 | 57 | [dependencies.pango] 58 | version = "0.18" 59 | features = ["v1_50"] 60 | 61 | -------------------------------------------------------------------------------- /src/models/event.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use crate::models::model::{Packet, ReceivedPacketInner, ReceivedSimpleFileInfo, ShareInfo, User}; 3 | 4 | pub enum UiEvent { 5 | UpdateUserListFooterStatus(String),//create_or_open_chat 6 | OpenOrReOpenChatWindow { 7 | name: String, 8 | ip: String 9 | }, 10 | UserListRemoveOne(String), 11 | UserListAddOne(User), 12 | CloseChatWindow(String), 13 | OpenOrReOpenChatWindow1 { name: String, ip: String, packet: Option}, 14 | DisplaySelfSendMsgInHis{to_ip: String, context: String, files: Option}, 15 | DisplayReceivedMsgInHis{from_ip: String, name: String, context: String, files: Vec }, 16 | RemoveInReceivedList {packet_id: u32, file_id: u32, download_ip: String }, 17 | } 18 | 19 | pub enum ModelEvent { 20 | UserListSelected(String), 21 | UserListDoubleClicked{ name: String, ip:String }, 22 | ReceivedPacket{ packet: Packet }, 23 | BroadcastEntry(Packet), 24 | RecMsgReply{ packet: Packet, from_ip: String}, 25 | BroadcastExit(String), 26 | RecOnlineMsgReply{ packet: Packet, from_user: User}, 27 | ClickChatWindowCloseBtn{from_ip: String}, 28 | NotifyOnline{ user: User}, 29 | ReceivedMsg{msg: ReceivedPacketInner}, 30 | SendOneMsg {to_ip: String, packet: Packet, context: String, files: Option}, 31 | PutInTcpFilePool(), 32 | DownloadIsBusy { file: ReceivedSimpleFileInfo }, 33 | PutDownloadTaskInPool { file: ReceivedSimpleFileInfo, save_base_path: PathBuf, download_ip: String}, 34 | RemoveDownloadTaskInPool { packet_id: u32, file_id: u32, download_ip: String}, 35 | } 36 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use encoding::{Encoding, EncoderTrap}; 2 | use encoding::all::GB18030; 3 | use chrono::Utc; 4 | use chrono::DateTime; 5 | use std::time::UNIX_EPOCH; 6 | use std::time::SystemTime; 7 | use chrono::TimeZone; 8 | use combine::error::ParseError; 9 | use combine::{many1, many, Parser, Stream, token, satisfy}; 10 | use crate::models::model::Packet; 11 | 12 | pub fn utf8_to_gb18030(ori_str : &str) -> Vec { 13 | GB18030.encode(&ori_str, EncoderTrap::Strict).unwrap() 14 | } 15 | 16 | pub fn system_time_to_date_time(t: SystemTime) -> DateTime { 17 | let dur = t.duration_since(UNIX_EPOCH).unwrap(); 18 | Utc.timestamp_opt(dur.as_secs() as i64, dur.subsec_nanos()).unwrap() 19 | } 20 | 21 | pub fn packet_parser() -> impl Parser 22 | where 23 | Input: Stream, 24 | Input::Error: ParseError, 25 | { 26 | ( 27 | many1(satisfy(|c| c != ':')), 28 | token(':'), 29 | many1(satisfy(|c| c != ':')), 30 | token(':'), 31 | many1(satisfy(|c| c != ':')), 32 | token(':'), 33 | many1(satisfy(|c| c != ':')), 34 | token(':'), 35 | many1(satisfy(|c| c != ':')), 36 | token(':'), 37 | many(satisfy(|c| true)), 38 | ).map(|(verson, _, send_temp, _, hostname, _, host, _, cmd, _, ext): (String, _, String, _, String, _, String, _, String, _, String)| { 39 | let add_ext = if ext.is_empty() { 40 | None 41 | }else{ 42 | Some(ext) 43 | }; 44 | Packet::from(verson, send_temp, hostname, host, cmd.parse::().unwrap(), add_ext) 45 | }) 46 | } -------------------------------------------------------------------------------- /src/constants/protocol.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings, unused)] 2 | /* header */ 3 | ///飞鸽版本 4 | pub const IPMSG_VERSION: u32 = 0x0001; 5 | ///默认端口号 6 | pub const IPMSG_DEFAULT_PORT: u32 = 0x0979; 7 | 8 | /* command */ 9 | /// 无操作 10 | pub const IPMSG_NOOPERATION: u32 = 0x00000000; 11 | ///上线(开始于广播此命令 12 | pub const IPMSG_BR_ENTRY: u32 = 0x00000001; 13 | ///下线(结束于广播此命令) 14 | pub const IPMSG_BR_EXIT: u32 = 0x00000002; 15 | ///通报新上线 16 | pub const IPMSG_ANSENTRY: u32 = 0x00000003; 17 | ///更改为离开状态 18 | pub const IPMSG_BR_ABSENCE: u32 = 0x00000004; 19 | 20 | 21 | ///搜寻有效的主机用户 22 | pub const IPMSG_BR_ISGETLIST: u32 = 0x00000010; 23 | ///主机列表发送通知 24 | pub const IPMSG_OKGETLIST: u32 = 0x00000011; 25 | ///主机列表发送请求 26 | pub const IPMSG_GETLIST: u32 = 0x00000012; 27 | ///主机列表发送 28 | pub const IPMSG_ANSLIST: u32 = 0x00000013; 29 | 30 | pub const IPMSG_BR_ISGETLIST2: u32 = 0x00000018; 31 | 32 | 33 | ///消息发送 34 | pub const IPMSG_SENDMSG: u32 = 0x00000020; 35 | ///消息收到确认 36 | pub const IPMSG_RECVMSG: u32 = 0x00000021; 37 | ///消息打开通知 38 | pub const IPMSG_READMSG: u32 = 0x00000030; 39 | ///消息丢弃通知 40 | pub const IPMSG_DELMSG: u32 = 0x00000031; 41 | 42 | ///消息打开确认通知(version-8中添加) 43 | pub const IPMSG_ANSREADMSG: u32 = 0x00000032; 44 | ///获得IPMSG版本信息 45 | pub const IPMSG_GETINFO: u32 = 0x00000040; 46 | ///发送IPMSG版本信息 47 | pub const IPMSG_SENDINFO: u32 = 0x00000041; 48 | 49 | ///获得缺席信息 50 | pub const IPMSG_GETABSENCEINFO: u32 = 0x00000050; 51 | ///发送缺席信息 52 | pub const IPMSG_SENDABSENCEINFO: u32 = 0x00000051; 53 | 54 | ///文件传输请求 55 | pub const IPMSG_GETFILEDATA: u32 = 0x00000060; 56 | ///丢弃附加文件 57 | pub const IPMSG_RELEASEFILES: u32 = 0x00000061; 58 | ///附着统计文件请求 59 | pub const IPMSG_GETDIRFILES: u32 = 0x00000062; 60 | 61 | ///获得RSA公钥 62 | pub const IPMSG_GETPUBKEY: u32 = 0x00000072; 63 | ///应答RSA公钥 64 | pub const IPMSG_ANSPUBKEY: u32 = 0x00000073; 65 | 66 | /* file types for fileattach command */ 67 | pub const IPMSG_FILE_REGULAR: u32 = 0x00000001; 68 | pub const IPMSG_FILE_DIR: u32 = 0x00000002; 69 | pub const IPMSG_FILE_RETPARENT: u32 = 0x00000003;// return parent directory 70 | pub const IPMSG_FILE_SYMLINK: u32 = 0x00000004; 71 | pub const IPMSG_FILE_CDEV: u32 = 0x00000005;// for UNIX 72 | pub const IPMSG_FILE_BDEV: u32 = 0x00000006;// for UNIX 73 | pub const IPMSG_FILE_FIFO: u32 = 0x00000007;// for UNIX 74 | pub const IPMSG_FILE_RESFORK: u32 = 0x00000010;// for mac 75 | 76 | /* file attribute options for fileattach command */ 77 | pub const IPMSG_FILE_RONLYOPT: u32 = 0x00000100; 78 | pub const IPMSG_FILE_HIDDENOPT: u32 = 0x00001000; 79 | pub const IPMSG_FILE_EXHIDDENOPT: u32 = 0x00002000;// for MacOS X 80 | pub const IPMSG_FILE_ARCHIVEOPT: u32 = 0x00004000; 81 | pub const IPMSG_FILE_SYSTEMOPT: u32 = 0x00008000; 82 | 83 | /* extend attribute types for fileattach command */ 84 | pub const IPMSG_FILE_CREATETIME: u32 = 0x00000016; 85 | pub const IPMSG_FILE_MTIME: u32 = 0x00000014; 86 | 87 | pub const FILELIST_SEPARATOR: char = '\u{7}'; 88 | pub const HOSTLIST_SEPARATOR: char = '\u{7}'; 89 | 90 | /* option or all command */ 91 | ///存在/缺席模式(成员识别命令中使用) 92 | pub const IPMSG_ABSENCEOPT: u32 = 0x00000100; 93 | ///服务器模式(预留) 94 | pub const IPMSG_SERVEROPT: u32 = 0x00000200; 95 | ///发送单个成员识别命令 96 | pub const IPMSG_DIALUPOPT: u32 = 0x00010000; 97 | ///附件 98 | pub const IPMSG_FILEATTACHOPT: u32 = 0x00200000; 99 | ///密码 100 | pub const IPMSG_ENCRYPTOPT: u32 = 0x00400000; 101 | ///全部使用utf-8 102 | pub const IPMSG_UTF8OPT: u32 = 0x00800000; 103 | ///兼容utf-8 104 | pub const IPMSG_CAPUTF8OPT: u32 = 0x01000000; 105 | ///加密的附件信息 106 | pub const IPMSG_ENCEXTMSGOPT: u32 = 0x04000000; 107 | ///支持图像 108 | pub const IPMSG_CLIPBOARDOPT: u32 = 0x08000000; 109 | pub const IPMSG_CAPFILEENC_OBSLT: u32 = 0x00001000; 110 | pub const IPMSG_CAPFILEENCOPT: u32 = 0x00040000; 111 | 112 | /* option for sendmsg command */ 113 | ///需要回信确认 114 | pub const IPMSG_SENDCHECKOPT: u32 = 0x00000100; 115 | //密封消息 116 | pub const IPMSG_SECRETOPT: u32 = 0x00000200; 117 | ///广播(报告) 118 | pub const IPMSG_BROADCASTOPT: u32 = 0x00000400; 119 | ///组播(多选) 120 | pub const IPMSG_MULTICASTOPT: u32 = 0x00000800; 121 | ///自动应答 122 | pub const IPMSG_AUTORETOPT: u32 = 0x00002000; 123 | ///重试标志(搜索HOSTLIST时使用) 124 | pub const IPMSG_RETRYOPT: u32 = 0x00004000; 125 | ///挂锁 126 | pub const IPMSG_PASSWORDOPT: u32 = 0x00008000; 127 | ///不保留日志 128 | pub const IPMSG_NOLOGOPT: u32 = 0x00020000; 129 | ///通知BR_ENTRY以外的成员 130 | pub const IPMSG_NOADDLISTOPT: u32 = 0x00080000; 131 | ///密封消息确认 132 | pub const IPMSG_READCHECKOPT: u32 = 0x00100000; 133 | pub const IPMSG_SECRETEXOPT: u32 = IPMSG_READCHECKOPT|IPMSG_SECRETOPT; 134 | 135 | pub const IPMSG_LIMITED_BROADCAST: &'static str = "255.255.255.255"; 136 | 137 | pub fn get_mode(command: u32) -> u32 { 138 | command & 0x000000ff 139 | } 140 | 141 | pub fn get_opt(command: u32) -> u32 { 142 | command & 0xffffff00 143 | } 144 | 145 | ///以下为程序部分 146 | pub const REPARENT_PATH: char = '.'; 147 | 148 | //报文分隔符 149 | pub const IPMSG_PACKET_DELIMITER: char = ':'; 150 | 151 | use ::hostname as host_name; 152 | 153 | use std::net::IpAddr; 154 | use once_cell::sync::Lazy; 155 | 156 | ///得到本地ip 157 | pub fn get_local_ip() -> IpAddr { 158 | local_ip_address::local_ip().expect("获取本地ip失败") 159 | } 160 | 161 | ///得到主机名 162 | pub fn get_host_name() -> String { 163 | host_name::get().unwrap().into_string().unwrap() 164 | } 165 | 166 | pub static HOST_NAME: Lazy = Lazy::new(||{ 167 | return get_host_name(); 168 | }); 169 | 170 | pub static LOCAL_IP: Lazy = Lazy::new(||{ 171 | return get_local_ip().to_string(); 172 | }); 173 | 174 | pub static ADDR: Lazy = Lazy::new(||{ 175 | return format!("{}{}", "0.0.0.0:", IPMSG_DEFAULT_PORT); 176 | }); 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/ui/chat_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0 8 | 520 9 | 250 10 | 11 | 12 | 5 13 | 5 14 | 15 | 16 | 1 17 | 200 18 | 350 19 | 20 | 21 | 1 22 | 0 23 | 0 24 | textbuffer2 25 | 0 26 | 27 | 28 | 29 | 0 30 | 0 31 | 2 32 | 2 33 | 34 | 35 | 36 | 37 | 38 | True 39 | False 40 | 41 | 42 | 清空 43 | 1 44 | 1 45 | 46 | 47 | 48 | 49 | 发送 50 | 1 51 | 1 52 | 53 | 54 | 55 | 0 56 | 3 57 | 58 | 59 | 60 | 61 | 62 | 1 63 | 160 64 | 150 65 | 66 | 67 | 1 68 | 69 | 70 | 71 | 72 | 73 | 74 | 2 75 | 2 76 | 2 77 | 78 | 79 | 80 | 81 | 82 | 1 83 | 350 84 | 80 85 | 86 | 87 | 1 88 | textbuffer1 89 | 90 | 91 | 92 | 0 93 | 2 94 | 2 95 | 96 | 97 | 98 | 99 | 100 | True 101 | False 102 | 103 | 104 | 选择文件 105 | 1 106 | 1 107 | 108 | 109 | 110 | 111 | 选择文件夹 112 | 1 113 | 1 114 | 115 | 116 | 117 | 1 118 | 3 119 | 120 | 121 | 122 | 123 | 124 | face-smile 125 | 2 126 | 127 | 2 128 | 0 129 | 130 | 131 | 132 | 133 | 134 | 1 135 | 136 | 137 | 1 138 | 139 | 140 | 141 | 142 | 143 | 144 | 2 145 | 1 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/models/model.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | use std::path::PathBuf; 3 | use crate::constants::protocol::{self, IPMSG_VERSION}; 4 | 5 | /// 6 | /// 数据包格式 7 | #[derive(Clone, Debug)] 8 | pub struct Packet { 9 | ///版本好 标准协议为1 10 | pub ver: String, 11 | ///数据包编号 12 | pub packet_no: String, 13 | ///发送者的昵称 14 | pub sender_name: String, 15 | ///发送者的主机名 16 | pub sender_host: String, 17 | ///命令字 18 | pub command_no: u32, 19 | ///附加数据 20 | pub additional_section: Option, 21 | ///发送者ip 22 | pub ip: String, 23 | } 24 | 25 | type ExtStr = String; 26 | 27 | trait ExtMsg { 28 | fn to_ext_msg() -> ExtStr; 29 | } 30 | 31 | #[derive(Default)] 32 | pub struct PacketBuilder { 33 | ///版本好 标准协议为1 34 | pub ver: String, 35 | ///数据包编号 36 | pub packet_no: String, 37 | ///发送者的昵称 38 | pub sender_name: String, 39 | ///发送者的主机名 40 | pub sender_host: String, 41 | ///命令字 42 | pub command_no: u32, 43 | ///扩展命令 44 | pub ext_commands: Vec, 45 | } 46 | 47 | impl PacketBuilder { 48 | ///命令 49 | fn command(command_no: u32) -> PacketBuilder { 50 | let local: DateTime = Local::now(); 51 | let mut packet_builder: PacketBuilder = Default::default(); 52 | packet_builder.ver = format!("{}", IPMSG_VERSION); 53 | packet_builder.packet_no = format!("{}", local.timestamp()); 54 | packet_builder.sender_name = protocol::HOST_NAME.clone(); 55 | packet_builder.sender_host = protocol::HOST_NAME.clone(); 56 | packet_builder.command_no = command_no; 57 | packet_builder 58 | } 59 | ///扩展命令 60 | fn command_opt(mut self, ext_command_no: u32) -> PacketBuilder { 61 | self.ext_commands.push(ext_command_no); 62 | self 63 | } 64 | 65 | /*fn finish(&self) -> Packet { 66 | 67 | }*/ 68 | } 69 | 70 | impl Packet { 71 | 72 | ///new packet 73 | pub fn new(command_no: u32, additional_section: Option) -> Packet { 74 | let local: DateTime = Local::now(); 75 | Packet { 76 | ver: format!("{}", IPMSG_VERSION), 77 | packet_no: format!("{}", local.timestamp()), 78 | sender_name: protocol::HOST_NAME.clone(), 79 | sender_host: protocol::HOST_NAME.clone(), 80 | command_no: command_no, 81 | additional_section: additional_section, 82 | ip: "".to_owned(), 83 | } 84 | } 85 | 86 | /// from attrs 生成packet 87 | pub fn from(ver: S, 88 | packet_no: S, 89 | sender_name: S, 90 | sender_host: S, 91 | command_no: u32, 92 | additional_section: Option) -> Packet where S: Into { 93 | Packet { 94 | ver: ver.into(), 95 | packet_no: packet_no.into(), 96 | sender_name: sender_name.into(), 97 | sender_host: sender_host.into(), 98 | command_no: command_no, 99 | additional_section: additional_section, 100 | ip: "".to_owned(), 101 | } 102 | } 103 | } 104 | 105 | impl ToString for Packet { 106 | fn to_string(&self) -> String { 107 | if let Some(ref ext_str) = self.additional_section { 108 | format!("{}:{}:{}:{}:{}:{}", 109 | self.ver, 110 | self.packet_no, 111 | self.sender_name, 112 | self.sender_host, 113 | self.command_no, 114 | ext_str) 115 | }else { 116 | format!("{}:{}:{}:{}:{}:{}", 117 | self.ver, 118 | self.packet_no, 119 | self.sender_name, 120 | self.sender_host, 121 | self.command_no, 122 | "") 123 | } 124 | } 125 | } 126 | 127 | #[derive(Clone, Debug)] 128 | pub struct User { 129 | pub name: String, 130 | pub host: String, 131 | pub ip: String, 132 | pub group: String, 133 | } 134 | 135 | impl User { 136 | pub fn new>(name :S, host :S, ip :S, group :S) -> User { 137 | User{ 138 | name: name.into(), 139 | host: host.into(), 140 | ip: ip.into(), 141 | group: group.into(), 142 | } 143 | } 144 | } 145 | 146 | #[derive(Clone, Debug, Eq, PartialEq)] 147 | pub enum Operate { 148 | ADD, REMOVE 149 | } 150 | 151 | #[derive(Clone, Debug)] 152 | pub struct OperUser{ 153 | pub user :User, 154 | pub oper: Operate, 155 | } 156 | 157 | impl OperUser { 158 | pub fn new(user: User, oper :Operate) -> OperUser{ 159 | OperUser{ 160 | user: user, 161 | oper: oper, 162 | } 163 | } 164 | } 165 | 166 | #[derive(Clone, Debug)] 167 | pub struct ShareInfo { 168 | //包编号 169 | pub packet_no: u32, 170 | // 要发送的目的机器列表 171 | pub host: String, 172 | // 要发送的目的机器个数 173 | pub host_cnt: u32, 174 | //transStat 175 | // 要传输的文件信息 176 | pub file_info: Vec, 177 | // 要传输的文件个数 178 | pub file_cnt: u32, 179 | //文件添加时间 180 | pub attach_time: NaiveTime, 181 | } 182 | 183 | #[derive(Clone, Debug)] 184 | pub struct FileInfo { 185 | //要传输文件id 186 | pub file_id: u32, 187 | //文件名 188 | pub file_name: PathBuf, 189 | pub name: String, 190 | //文件的属性,如是文件或者文件夹,只读等 191 | pub attr: u8,// 1 普通文件 2 文件夹 192 | //文件大小 193 | pub size: u64, 194 | //文件最后一次修改时间 195 | pub mtime: NaiveTime, 196 | //文件最后一次访问时间 197 | pub atime: NaiveTime, 198 | //文件创建时间 199 | pub crtime: NaiveTime 200 | } 201 | 202 | impl FileInfo { 203 | pub fn to_fileinfo_msg(&self) -> String { 204 | self.file_name.as_path().file_name() 205 | .and_then(|name| { name.to_str() }) 206 | .map(|file_name| { format!("{}:{}:{:x}:{:x}:{}:", self.file_id, file_name, self.size, self.mtime.second(), self.attr) }).unwrap() 207 | 208 | } 209 | } 210 | 211 | 212 | #[derive(Clone, Debug)] 213 | pub struct ReceivedSimpleFileInfo { 214 | //要传输文件id 215 | pub file_id: u32, 216 | pub packet_id: u32, 217 | pub name: String, 218 | pub attr: u8,// 1 普通文件 2 文件夹 219 | pub size: u64, 220 | pub mtime: i64, 221 | } 222 | 223 | #[derive(Clone, Debug)] 224 | pub struct ReceivedPacketInner { 225 | ///发送者ip 226 | pub ip: String, 227 | ///原始packet 228 | pub packet: Option, 229 | ///文件列表 230 | pub opt_files: Option>, 231 | } 232 | 233 | impl ReceivedPacketInner { 234 | pub fn new>(ip: S) -> ReceivedPacketInner { 235 | ReceivedPacketInner { 236 | ip: ip.into(), 237 | packet: None, 238 | opt_files: None, 239 | } 240 | } 241 | 242 | pub fn packet(mut self, packet: Packet) -> ReceivedPacketInner { 243 | self.packet = Some(packet); 244 | self 245 | } 246 | 247 | pub fn opt_files(mut self, opt_files: Vec) -> ReceivedPacketInner { 248 | self.opt_files = Some(opt_files); 249 | self 250 | } 251 | 252 | pub fn option_opt_files(mut self, opt_files: Option>) -> ReceivedPacketInner { 253 | self.opt_files = opt_files; 254 | self 255 | } 256 | } 257 | 258 | pub struct ErrMsg { 259 | pub msg: String, 260 | pub fatal: bool, 261 | } 262 | -------------------------------------------------------------------------------- /src/core/download.rs: -------------------------------------------------------------------------------- 1 | use std::io::prelude::*; 2 | use std::net::TcpStream; 3 | use std::io::BufReader; 4 | use std::path::{Path, PathBuf}; 5 | use std::thread; 6 | use std::fs::{self, File}; 7 | use std::net::ToSocketAddrs; 8 | use std::sync::{Arc, Mutex}; 9 | use std::collections::HashMap; 10 | use encoding::{Encoding, DecoderTrap}; 11 | use encoding::all::GB18030; 12 | use log::{info, debug}; 13 | use anyhow::{Result, anyhow}; 14 | use crate::constants::protocol::{self, IPMSG_SENDMSG, IPMSG_GETFILEDATA, IPMSG_GETDIRFILES, IPMSG_FILE_DIR, IPMSG_FILE_REGULAR, IPMSG_FILE_RETPARENT, IPMSG_PACKET_DELIMITER}; 15 | use crate::core::GLOBLE_SENDER; 16 | use crate::models::event::ModelEvent; 17 | use crate::models::model::{Packet, ReceivedSimpleFileInfo}; 18 | 19 | #[derive(Clone, Debug)] 20 | pub struct ManagerPool { 21 | pub file_pool: Arc>>, 22 | } 23 | 24 | impl ManagerPool { 25 | 26 | pub fn new(file_pool: Arc>>) -> ManagerPool { 27 | ManagerPool { file_pool} 28 | } 29 | 30 | pub fn run(mut self, file_info: ReceivedSimpleFileInfo, save_path: PathBuf, download_ip: String) { 31 | let tmp = self.clone(); 32 | { 33 | let mut lock = tmp.file_pool.lock().unwrap(); 34 | let file = lock.get(&file_info.file_id); 35 | if let Some(p_file) = file { 36 | if p_file.status == 1 { 37 | //下载中 38 | GLOBLE_SENDER.send(ModelEvent::DownloadIsBusy{ file: file_info }).expect("send DownloadIsBusy fail!"); 39 | return; 40 | } 41 | }else { 42 | lock.insert(file_info.file_id, PoolFile { status: 1, file_info: file_info.clone() }); 43 | } 44 | } 45 | let tmp = self.clone(); 46 | thread::spawn(move || { 47 | let download_url = format!("{}:{}", download_ip, protocol::IPMSG_DEFAULT_PORT); 48 | let is_ok = download(download_url, save_path, file_info.clone()).is_ok(); 49 | { 50 | let mut lock = tmp.file_pool.lock().unwrap(); 51 | let mut file = lock.get(&file_info.file_id); 52 | if let Some(p_file) = file.take() { 53 | if is_ok { 54 | lock.remove(&file_info.file_id); 55 | GLOBLE_SENDER.send(ModelEvent::RemoveDownloadTaskInPool{ packet_id: file_info.packet_id, file_id: file_info.file_id, download_ip }).expect("send RemoveDownloadTaskInPool fail!"); 56 | }else{ 57 | let mut tmp_file = p_file.clone(); 58 | tmp_file.status = 0; 59 | lock.insert(tmp_file.file_info.file_id, tmp_file); 60 | } 61 | 62 | } 63 | } 64 | 65 | }); 66 | } 67 | 68 | } 69 | 70 | #[derive(Clone, Debug)] 71 | pub struct PoolFile { 72 | pub status: u8, //0 初始 1 下载中 73 | pub file_info: ReceivedSimpleFileInfo, 74 | } 75 | 76 | pub fn download>(addr: A, to_path: S, r_file: ReceivedSimpleFileInfo) -> Result<()> { 77 | info!("start download file"); 78 | let file_type = r_file.attr as u32; 79 | let mut stream = TcpStream::connect(addr)?; 80 | let packet = Packet::new(IPMSG_SENDMSG| if file_type == IPMSG_FILE_DIR { IPMSG_GETDIRFILES } else { IPMSG_GETFILEDATA }, Some(format!("{:x}:{:x}:0:\u{0}", r_file.packet_id, r_file.file_id))); 81 | stream.write(packet.to_string().as_bytes())?; 82 | debug!("filetype {}", file_type); 83 | if file_type == IPMSG_FILE_REGULAR { 84 | let mut file_location = to_path.as_ref().to_path_buf(); 85 | file_location.push(r_file.name); 86 | let file_size = r_file.size; 87 | let mut buffer = BufReader::new(stream); 88 | read_bytes_to_file(&mut buffer, file_size, &file_location)?; 89 | }else if file_type == IPMSG_FILE_DIR { 90 | let mut next_path = to_path.as_ref().to_path_buf(); 91 | let mut buffer = BufReader::new(stream); 92 | while let Ok(Some(header_size_str)) = read_delimiter(&mut buffer) { 93 | let header_size = u64::from_str_radix(&header_size_str, 16)?; 94 | info!("header_size {:?}", header_size); 95 | let header_context_str = read_bytes(&mut buffer, (header_size - 1 - header_size_str.as_bytes().len() as u64))?;//-1是减去的那个冒号 96 | let v: Vec<&str> = header_context_str.splitn(4, |c| c == ':').collect(); 97 | let file_name = v[0]; 98 | let file_size = u64::from_str_radix(v[1], 16)?; 99 | let file_attr = u32::from_str_radix(v[2], 16)?; 100 | let opt = protocol::get_opt(file_attr); 101 | let cmd = protocol::get_mode(file_attr); 102 | info!("header context {:?}", v); 103 | if cmd == IPMSG_FILE_DIR { 104 | next_path.push(file_name); 105 | if !next_path.exists() { 106 | fs::create_dir(&next_path)?; 107 | } 108 | info!("crate dir{:?}", next_path); 109 | }else if cmd == IPMSG_FILE_REGULAR { 110 | next_path.push(file_name); 111 | info!("crate file{:?}", next_path); 112 | read_bytes_to_file(&mut buffer, file_size, &next_path)?; 113 | next_path.pop(); 114 | }else if cmd == IPMSG_FILE_RETPARENT { 115 | next_path.pop(); 116 | info!("back to parent {:?}", next_path); 117 | }else { 118 | 119 | } 120 | } 121 | } 122 | info!("download end!"); 123 | Ok(()) 124 | } 125 | 126 | fn read_delimiter(mut stream : & mut BufReader) -> Result> { 127 | let mut s_buffer = Vec::new(); 128 | let len = stream.read_until(u8::try_from(IPMSG_PACKET_DELIMITER)?, &mut s_buffer)?; 129 | if len != 0usize { 130 | if len > 200 { 131 | return Err(anyhow!("read_delimiter error!")); 132 | }else { 133 | s_buffer.pop(); 134 | Ok(Some(String::from_utf8(s_buffer).unwrap())) 135 | } 136 | }else { 137 | Ok(None) 138 | } 139 | } 140 | 141 | fn read_bytes(mut stream : & mut BufReader, len: u64) -> Result { 142 | let mut s_buffer = Vec::new(); 143 | let mut handler = stream.take(len); 144 | handler.read_to_end(&mut s_buffer)?; 145 | Ok(GB18030.decode(s_buffer.as_slice(), DecoderTrap::Ignore).map_err(|e| anyhow!("{:?}", e))?) 146 | } 147 | 148 | fn read_bytes_to_file(mut stream : & mut BufReader, len: u64, file_path: &PathBuf) -> Result<()> { 149 | let mut f: File = File::create(file_path).unwrap(); 150 | info!("file len {:?}", len); 151 | let mut handler = stream.take(len as u64); 152 | let mut buf = [0; 1024 * 4]; 153 | while let Ok(bytes_read) = handler.read(&mut buf) { 154 | if bytes_read == 0 { break; } 155 | f.write(&buf[..bytes_read])?; 156 | } 157 | Ok(()) 158 | } 159 | 160 | /// 161 | /// unkown filesize can use follow 162 | /// 可以不需要文件长度的读 163 | /// 164 | fn read_bytes_to_file_unsize(mut stream : & mut BufReader, file_path: &PathBuf) -> Result<()> { 165 | let mut file: File = File::create(file_path).unwrap(); 166 | loop { 167 | let mut buffer = [0; 2048]; 168 | let num = stream.read(&mut buffer[..])?; 169 | if num == 0 { 170 | break; 171 | } 172 | file.write(&buffer[0..num])?; 173 | } 174 | Ok(()) 175 | } -------------------------------------------------------------------------------- /src/core/fileserver.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::sync::{Mutex, Arc}; 3 | use std::net::{TcpStream, TcpListener}; 4 | use std::thread; 5 | use std::io::{Read, Write, BufWriter}; 6 | use std::path::PathBuf; 7 | use std::fs::{self, File, Metadata}; 8 | use encoding::{Encoding, DecoderTrap}; 9 | use encoding::all::GB18030; 10 | use chrono::prelude::*; 11 | use log::{info, debug}; 12 | use combine::parser::Parser; 13 | use crate::models::model::ShareInfo; 14 | use crate::{constants::protocol, util}; 15 | use crate::constants::protocol::{IPMSG_PACKET_DELIMITER, REPARENT_PATH}; 16 | use crate::util::packet_parser; 17 | 18 | #[derive(Clone, Debug)] 19 | pub struct FileServer { 20 | pub file_pool: Arc>> 21 | } 22 | 23 | impl FileServer { 24 | 25 | pub fn new(file_pool: Arc>>) -> FileServer { 26 | FileServer{ 27 | file_pool 28 | } 29 | } 30 | 31 | pub fn run(&self) { 32 | let pool_tmp = self.file_pool.clone(); 33 | thread::spawn(move || { 34 | let tcp_listener: TcpListener = TcpListener::bind(protocol::ADDR.as_str()).unwrap(); 35 | let pool_tmp = pool_tmp.clone(); 36 | info!("tcp server start listening! {:?}", protocol::ADDR.as_str()); 37 | for stream in tcp_listener.incoming() { 38 | let base_stream = stream.unwrap().try_clone().unwrap(); 39 | //let search_arc = search_arc_tmp.clone(); 40 | let pool_tmp = pool_tmp.clone(); 41 | let src = base_stream.peer_addr().unwrap(); 42 | thread::spawn(move || { 43 | let mut stream_echo = base_stream; 44 | let mut buf = [0; 2048]; 45 | let byte_size = stream_echo.read(&mut buf[..]).unwrap(); 46 | let mut tmp_buf = &buf[0..byte_size]; 47 | let tmp_str = GB18030.decode(&tmp_buf, DecoderTrap::Strict).unwrap(); 48 | info!("file_processer receive raw str {:?}", tmp_str); 49 | let result = packet_parser().parse(tmp_str.as_str()); 50 | match result { 51 | Ok((mut packet, _)) => { 52 | packet.ip = src.ip().to_string(); 53 | let cmd = protocol::get_mode(packet.command_no); 54 | if packet.additional_section.is_some() { 55 | if cmd == protocol::IPMSG_GETFILEDATA { 56 | //文件请求 57 | FileServer::process_file(&pool_tmp, &mut stream_echo, packet.additional_section.unwrap()) 58 | }else if cmd == protocol::IPMSG_GETDIRFILES { 59 | FileServer::process_dir(pool_tmp, stream_echo, packet.additional_section.unwrap()) 60 | }else { 61 | info!("Invalid packet tcp file cmd {:?} !", tmp_str); 62 | } 63 | }else{ 64 | info!("Invalid packet additional_section is none {:?} !", tmp_str); 65 | } 66 | } 67 | Err(_) => { 68 | info!("Invalid packet tcp file cmd {:?} !", tmp_str); 69 | } 70 | } 71 | }); 72 | } 73 | }); 74 | } 75 | 76 | fn process_dir(pool_tmp: Arc>>, mut stream_echo: TcpStream, ext_str: String) -> () { 77 | let file_attr = ext_str.splitn(3, |c| c == ':').into_iter().filter(|x: &&str| !x.is_empty()).collect::>(); 78 | info!("file dir packet parse {:?}", file_attr); 79 | if file_attr.len() >= 2 { 80 | let packet_id = i64::from_str_radix(file_attr[0], 16).unwrap() as u32; 81 | let file_id = i64::from_str_radix(file_attr[1], 16).unwrap(); 82 | let mut search_result: Option = Option::None; 83 | { 84 | let search = pool_tmp.lock().unwrap(); 85 | let ref vec: Vec = *search; 86 | let result = vec.iter().find(|ref s| s.packet_no == packet_id); 87 | search_result = result.cloned(); 88 | } 89 | if let Some(result_share_file) = search_result { 90 | let file_info = result_share_file.file_info.iter().find(|ref f| f.file_id == file_id as u32); 91 | if let Some(file_info) = file_info { 92 | let ref root_path: PathBuf = file_info.file_name; 93 | let mut buffer = BufWriter::new(stream_echo.try_clone().unwrap()); 94 | send_dir(root_path, &mut buffer); 95 | } 96 | } 97 | } 98 | } 99 | 100 | fn process_file(pool_tmp: &Arc>>, mut stream_echo: &mut TcpStream, ext_str: String) -> () { 101 | let file_attr = ext_str.splitn(4, |c| c == ':').into_iter().filter(|x: &&str| !x.is_empty()).collect::>(); 102 | info!("file packet parse {:?}", file_attr); 103 | if file_attr.len() >= 3 { 104 | let packet_id = i64::from_str_radix(file_attr[0], 16).unwrap() as u32; 105 | let file_id = i64::from_str_radix(file_attr[1], 16).unwrap(); 106 | let offset = file_attr[2].parse::().unwrap(); 107 | let mut search_result: Option = Option::None; 108 | { 109 | let search = pool_tmp.lock().unwrap(); 110 | let ref vec: Vec = *search; 111 | let result = vec.iter().find(|ref s| s.packet_no == packet_id); 112 | search_result = result.cloned(); 113 | } 114 | if let Some(result_share_file) = search_result { 115 | let file_info = result_share_file.file_info.iter().find(|f| f.file_id == file_id as u32); 116 | if let Some(file_info) = file_info { 117 | let mut f: File = File::open(&file_info.file_name).unwrap(); 118 | let mut buf = [0; 1024]; 119 | let mut buffer = BufWriter::new(stream_echo); 120 | while let Ok(bytes_read) = f.read(&mut buf) { 121 | if bytes_read == 0 { break; } 122 | buffer.write(&buf[..bytes_read]).unwrap(); 123 | buffer.flush().unwrap(); 124 | } 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | //send dir 132 | pub fn send_dir(root_path: &PathBuf, mut buffer : & mut BufWriter) { 133 | buffer.write(util::utf8_to_gb18030(&make_header(&root_path, false)).as_slice()).unwrap();//root dir 134 | debug!("{:?}", make_header(&root_path, false)); 135 | if root_path.is_dir() { 136 | for sub_path in fs::read_dir(root_path).unwrap() { 137 | let sub = sub_path.unwrap().path(); 138 | if sub.is_file() { 139 | let header = make_header(&sub, false); 140 | buffer.write(util::utf8_to_gb18030(&header).as_slice()).unwrap(); 141 | info!("{:?}", header); 142 | let mut buf = [0; 1024]; 143 | let mut f: File = File::open(&sub).unwrap(); 144 | while let Ok(bytes_read) = f.read(&mut buf) { 145 | if bytes_read == 0 { break; } 146 | buffer.write(&buf[..bytes_read]).unwrap(); 147 | buffer.flush().unwrap(); 148 | } 149 | }else { 150 | send_dir(&sub, &mut buffer); 151 | } 152 | } 153 | } 154 | let ret_parent = make_header(root_path, true); 155 | buffer.write(ret_parent.as_bytes()).unwrap(); 156 | debug!("{ret_parent:?}"); 157 | } 158 | 159 | /// 160 | /// 转换报文 161 | pub fn make_header(path: &PathBuf, ret_parent: bool) -> String { 162 | let file_name; 163 | let file_attr; 164 | let file_size; 165 | let mut header = String::new(); 166 | header.push(IPMSG_PACKET_DELIMITER); 167 | if ret_parent { 168 | file_attr = protocol::IPMSG_FILE_RETPARENT; 169 | let tmp_file_name = format!("{}", REPARENT_PATH); 170 | header.push_str(tmp_file_name.as_str());//filename 171 | file_size = 0; 172 | }else{ 173 | let path_metadata: Metadata = fs::metadata(&path).unwrap(); 174 | file_size = path_metadata.len(); 175 | file_name = path.file_name().unwrap().to_str().unwrap(); 176 | header.push_str(file_name); 177 | if path_metadata.is_dir() { 178 | file_attr = protocol::IPMSG_FILE_DIR; 179 | } else { 180 | file_attr = protocol::IPMSG_FILE_REGULAR; 181 | } 182 | path_metadata.created(); 183 | path_metadata.modified(); 184 | } 185 | 186 | header.push(IPMSG_PACKET_DELIMITER); 187 | header.push_str(format!("{:x}", file_size).as_str());//filesize// 188 | header.push(IPMSG_PACKET_DELIMITER); 189 | header.push_str(format!("{:x}", file_attr).as_str());//fileattr 190 | let timestamp_now = Local::now().timestamp(); 191 | header.push_str(format!(":{:x}={:x}:{:x}={:x}:", protocol::IPMSG_FILE_CREATETIME, timestamp_now, protocol::IPMSG_FILE_MTIME, timestamp_now).as_str());// 192 | let mut length = util::utf8_to_gb18030(&header).len(); 193 | length = length + format!("{:0>4x}", length).len(); 194 | header.insert_str(0, format!("{:0>4x}", length).as_str()); 195 | header 196 | } -------------------------------------------------------------------------------- /src/ui/chat_window.rs: -------------------------------------------------------------------------------- 1 | use gtk::prelude::*; 2 | use gtk::{ 3 | CellRendererText, Window, 4 | ListStore, TreeView, TreeViewColumn, Builder, Button, TextView, WrapMode 5 | }; 6 | use std::sync::Arc; 7 | use std::cell::RefCell; 8 | use std::path::PathBuf; 9 | use std::fs::{self, Metadata}; 10 | use std::time::{self}; 11 | use chrono::prelude::*; 12 | use log::info; 13 | use glib::clone; 14 | use crate::core::GLOBLE_SENDER; 15 | use crate::models::model::{self, ReceivedSimpleFileInfo}; 16 | use crate::models::event::ModelEvent; 17 | use crate::models::message; 18 | //use crate::app::GLOBAL_CHATWINDOWS; 19 | 20 | // make moving clones into closures more convenient 21 | 22 | 23 | #[derive(Clone)] 24 | pub struct ChatWindow { 25 | pub win :Window, 26 | pub his_view :TextView, 27 | pub ip :String, 28 | pub pre_send_files :Arc>>, 29 | pub received_store :ListStore, 30 | } 31 | 32 | pub fn create_chat_window>(name :S, host_ip :S) -> ChatWindow { 33 | let name: String = name.into(); 34 | let host_ip: String = host_ip.into(); 35 | let chat_title = &format!("和{}({})聊天窗口", name, host_ip); 36 | 37 | let glade_src = include_str!("chat_window.ui"); 38 | let builder = Builder::new(); 39 | builder.add_from_string(glade_src).unwrap(); 40 | 41 | let chat_window: Window = builder.object("chat_window").unwrap(); 42 | chat_window.set_title(Some(chat_title)); 43 | // chat_window.set_border_width(5); 44 | //历史 45 | let text_view_history: TextView = builder.object("text_view_history").unwrap(); 46 | //待发送的 47 | let text_view_presend: TextView = builder.object("text_view_presend").unwrap(); 48 | //待发送文件 49 | let tree_view_presend: TreeView = builder.object("tree_view_presend").unwrap(); 50 | //接受的文件 51 | let tree_view_received: TreeView = builder.object("tree_view_received").unwrap(); 52 | 53 | text_view_history.set_wrap_mode(WrapMode::WordChar); 54 | text_view_presend.set_wrap_mode(WrapMode::WordChar); 55 | append_column(&tree_view_presend, 0, "待发送文件"); 56 | append_column(&tree_view_received, 0, "收到的文件"); 57 | 58 | let btn_clear: Button = builder.object("btn_clear").unwrap(); 59 | let btn_send: Button = builder.object("btn_send").unwrap();//btn_file 60 | let btn_file: Button = builder.object("btn_file").unwrap(); 61 | let btn_dir: Button = builder.object("btn_dir").unwrap(); 62 | 63 | //let text_view_presend_clone = text_view_presend.clone(); 64 | let text_view_history_clone = text_view_history.clone(); 65 | //let arc_received_files: Arc>> = Arc::new(RefCell::new(Vec::new())); 66 | let pre_send_files: Arc>> = Arc::new(RefCell::new(Vec::new()));//待发送文件列表 67 | let pre_send_files_model = create_and_fill_model(); 68 | tree_view_presend.set_model(Some(&pre_send_files_model)); 69 | //let pre_send_files_model_send = pre_send_files_model.clone(); 70 | let pre_received_files_model = create_and_fill_model1(); 71 | tree_view_received.set_model(Some(&pre_received_files_model)); 72 | let files_send_clone = pre_send_files.clone(); 73 | btn_send.connect_clicked(clone!(@strong pre_send_files_model, @strong host_ip, @strong text_view_presend => move|_|{ 74 | let (start_iter, mut end_iter) = text_view_presend.buffer().bounds(); 75 | let context :&str = &text_view_presend.buffer().text(&start_iter, &end_iter, false); 76 | let (packet, share_file) = message::create_sendmsg(context.to_owned(), files_send_clone.clone().borrow().to_vec(), host_ip.clone()); 77 | GLOBLE_SENDER.send(ModelEvent::SendOneMsg {to_ip: host_ip.clone(), packet, context: context.to_owned(), files: share_file}).unwrap(); 78 | files_send_clone.borrow_mut().clear(); 79 | pre_send_files_model.clear(); 80 | text_view_presend.buffer().set_text(""); 81 | })); 82 | 83 | let chat_window_open_save = chat_window.clone(); 84 | tree_view_received.connect_row_activated(clone!(@weak chat_window_open_save, @strong host_ip => move |tree_view, tree_path, tree_view_column| { 85 | let selection = tree_view.selection(); 86 | if let Some((model, iter)) = selection.selected() { 87 | let name: String = model.get_value(&iter, 0).get().unwrap(); 88 | let fid: u32 = model.get_value(&iter, 1).get().unwrap(); 89 | let pid: u32 = model.get_value(&iter, 2).get().unwrap(); 90 | let file_type: u8 = model.get_value(&iter, 3).get().unwrap(); 91 | let size: u64 = model.get_value(&iter, 4).get().unwrap(); 92 | let mtime: i64 = model.get_value(&iter, 5).get().unwrap(); 93 | 94 | let file_chooser = gtk::FileChooserDialog::new( 95 | Some("保存文件"), 96 | Some(&chat_window_open_save), 97 | gtk::FileChooserAction::SelectFolder, 98 | &[("保存", gtk::ResponseType::Ok), ("取消", gtk::ResponseType::Cancel)], 99 | ); 100 | 101 | let host_ip = host_ip.clone(); 102 | file_chooser.connect_response(move |d: >k::FileChooserDialog, response: gtk::ResponseType| { 103 | if response == gtk::ResponseType::Ok { 104 | let file = d.file().expect("Couldn't get file"); 105 | let save_base_path: PathBuf = file.path().expect("Couldn't get file path"); 106 | info!("choosed {:?} {:?} {:?} {:?} {:?}", name, fid, pid, save_base_path, file_type); 107 | GLOBLE_SENDER.send(ModelEvent::PutDownloadTaskInPool{ file: ReceivedSimpleFileInfo{ 108 | file_id: fid, 109 | packet_id: pid, 110 | name: name.clone(), 111 | attr: file_type, 112 | size, 113 | mtime 114 | }, save_base_path, download_ip: host_ip.clone() }); 115 | } 116 | d.close(); 117 | }); 118 | file_chooser.show(); 119 | } 120 | })); 121 | 122 | btn_clear.connect_clicked(clone!(@strong text_view_presend => move|_|{ 123 | text_view_presend.buffer().set_text(""); 124 | })); 125 | 126 | let chat_window_open_file = chat_window.clone(); 127 | let pre_send_files_open_file = pre_send_files.clone(); 128 | let chat_window_open_save = chat_window_open_save.clone(); 129 | btn_file.connect_clicked(clone!(@strong pre_send_files_model, @weak chat_window_open_save => move|_|{ 130 | 131 | let file_chooser = gtk::FileChooserDialog::new( 132 | Some("打开文件"), 133 | Some(&chat_window_open_save), 134 | gtk::FileChooserAction::Open, 135 | &[("选择文件", gtk::ResponseType::Ok), ("取消", gtk::ResponseType::Cancel)], 136 | ); 137 | 138 | let pre_send_files_open_file = pre_send_files_open_file.clone(); 139 | let pre_send_files_model = pre_send_files_model.clone(); 140 | let file_chooser_tmp = file_chooser.clone(); 141 | file_chooser.connect_response(move |d: >k::FileChooserDialog, response: gtk::ResponseType| { 142 | if response == gtk::ResponseType::Ok { 143 | let filename: PathBuf = file_chooser_tmp.clone().file().unwrap().path().unwrap(); 144 | let metadata: Metadata = fs::metadata(&filename).unwrap(); 145 | let size = metadata.len(); 146 | let attr = if metadata.is_file() { 147 | crate::constants::protocol::IPMSG_FILE_REGULAR 148 | }else if metadata.is_dir() { 149 | crate::constants::protocol::IPMSG_FILE_DIR 150 | }else { 151 | panic!("oh no!"); 152 | }; 153 | let modify_time: time::SystemTime = metadata.modified().unwrap(); 154 | let chrono_time = crate::util::system_time_to_date_time(modify_time); 155 | let local_time = chrono_time.with_timezone(&::chrono::Local); 156 | let name = filename.file_name().unwrap().to_str().unwrap(); 157 | let file_info = model::FileInfo { 158 | file_id: Local::now().timestamp() as u32, 159 | file_name: filename.clone(), 160 | name: name.to_owned(), 161 | attr: attr as u8, 162 | size: size, 163 | mtime: Local::now().time(), 164 | atime: Local::now().time(), 165 | crtime: Local::now().time() 166 | }; 167 | let ref mut files_add = *pre_send_files_open_file.borrow_mut(); 168 | files_add.push(file_info.clone());//添加待发送文件 169 | //pre_send_files_model.insert_with_values(None, &[0, 1], &[&&name, &format!("{}", &file_info.file_id)]); 170 | pre_send_files_model.insert_with_values(None, &[(0, &&name), (1, &format!("{}", &file_info.file_id))]); 171 | } 172 | d.close(); 173 | }); 174 | file_chooser.show(); 175 | })); 176 | 177 | let chat_window_open_dir = chat_window.clone(); 178 | let pre_send_files_open_dir = pre_send_files.clone(); 179 | let chat_window_open_save = chat_window_open_save.clone(); 180 | btn_dir.connect_clicked(clone!(@strong pre_send_files_model, @strong pre_send_files, @weak chat_window_open_save => move|_|{ 181 | let file_chooser = gtk::FileChooserDialog::new( 182 | Some("打开文件夹"), 183 | Some(&chat_window_open_save), 184 | gtk::FileChooserAction::SelectFolder, 185 | &[("选择文件夹", gtk::ResponseType::Ok), ("取消", gtk::ResponseType::Cancel)], 186 | ); 187 | let pre_send_files_open_dir = pre_send_files.clone(); 188 | let pre_send_files_model = pre_send_files_model.clone(); 189 | let file_chooser_tmp = file_chooser.clone(); 190 | file_chooser.connect_response(move |d: >k::FileChooserDialog, response: gtk::ResponseType| { 191 | if response == gtk::ResponseType::Ok { 192 | let filename: PathBuf = file_chooser_tmp.file().unwrap().path().unwrap(); 193 | let metadata: Metadata = fs::metadata(&filename).unwrap(); 194 | let size = metadata.len(); 195 | let attr = if metadata.is_file() { 196 | crate::constants::protocol::IPMSG_FILE_REGULAR 197 | }else if metadata.is_dir() { 198 | crate::constants::protocol::IPMSG_FILE_DIR 199 | }else { 200 | panic!("oh no!"); 201 | }; 202 | let modify_time: time::SystemTime = metadata.modified().unwrap(); 203 | let chrono_time = crate::util::system_time_to_date_time(modify_time); 204 | let local_time = chrono_time.with_timezone(&::chrono::Local); 205 | let name = filename.file_name().unwrap().to_str().unwrap(); 206 | let file_info = model::FileInfo { 207 | file_id: Local::now().timestamp() as u32, 208 | file_name: filename.clone(), 209 | name: name.to_owned(), 210 | attr: attr as u8, 211 | size: size, 212 | mtime: Local::now().time(), 213 | atime: Local::now().time(), 214 | crtime: Local::now().time(), 215 | }; 216 | let ref mut files_add = *pre_send_files_open_dir.borrow_mut(); 217 | files_add.push(file_info.clone());//添加待发送文件 218 | pre_send_files_model.insert_with_values(None, &[(0, &name), (1, &format!("{}", &file_info.file_id))]); 219 | } 220 | d.close(); 221 | }); 222 | file_chooser.show(); 223 | })); 224 | 225 | chat_window.connect_close_request(clone!(@strong host_ip => @default-return glib::signal::Inhibit(false), move |window| { 226 | GLOBLE_SENDER.send(ModelEvent::ClickChatWindowCloseBtn{from_ip: host_ip.clone()}).unwrap(); 227 | /*if let Some(application) = window.application() { 228 | application.remove_window(window);*/ 229 | window.destroy(); 230 | info!("关闭聊天窗口!"); 231 | // } 232 | glib::Propagation::Proceed 233 | })); 234 | 235 | 236 | // chat_window.show_all(); 237 | chat_window.present(); 238 | let clone_chat = chat_window.clone(); 239 | let clone_hist_view = text_view_history.clone(); 240 | ChatWindow{ win: clone_chat, his_view: clone_hist_view, ip: host_ip, pre_send_files, received_store: pre_received_files_model} 241 | } 242 | 243 | fn append_column(tree: &TreeView, id: i32, title: &str) { 244 | let column = TreeViewColumn::new(); 245 | let cell = CellRendererText::new(); 246 | column.pack_start(&cell, true); 247 | column.set_title(title); 248 | column.add_attribute(&cell, "text", id); 249 | tree.append_column(&column); 250 | tree.set_headers_visible(true); 251 | } 252 | 253 | fn create_and_fill_model() -> ListStore { 254 | let model = ListStore::new(&[String::static_type(), String::static_type()]); 255 | model 256 | } 257 | 258 | fn create_and_fill_model1() -> ListStore { 259 | let model = ListStore::new(&[String::static_type(), u32::static_type(), u32::static_type(), u8::static_type(), u64::static_type(), i64::static_type()]); 260 | model 261 | } 262 | 263 | /* 264 | /// ip 265 | fn modify_received_list(received_store :Option, received_files: Arc>>) -> Continue { 266 | 267 | Continue(false) 268 | }*/ -------------------------------------------------------------------------------- /src/events/model.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use std::sync::{Arc, Mutex}; 3 | use std::net::UdpSocket; 4 | use encoding::{DecoderTrap, Encoding}; 5 | use encoding::all::GB18030; 6 | use chrono::prelude::*; 7 | use std::collections::HashMap; 8 | use async_channel::Sender; 9 | use log::{error, info}; 10 | use combine::parser::Parser; 11 | use crate::models::model::{Packet, ReceivedPacketInner, ReceivedSimpleFileInfo, ShareInfo, User}; 12 | use crate::core::download::{ManagerPool, PoolFile}; 13 | use crate::core::fileserver::FileServer; 14 | use crate::models::event::{ModelEvent, UiEvent}; 15 | use crate::constants::protocol::{self, IPMSG_BR_ENTRY, IPMSG_BROADCASTOPT, IPMSG_DEFAULT_PORT, IPMSG_LIMITED_BROADCAST}; 16 | use crate::core::{GLOBLE_RECEIVER, GLOBLE_SENDER}; 17 | use crate::util::packet_parser; 18 | 19 | pub fn model_run(socket: UdpSocket, ui_event_sender: Sender) { 20 | 21 | let file_pool: Arc>> = Arc::new(Mutex::new(Vec::new())); 22 | 23 | let file_server = FileServer::new(file_pool.clone()); 24 | 25 | file_server.run(); 26 | 27 | let download_pool: Arc>> = Arc::new(Mutex::new(HashMap::new())); 28 | 29 | let manager_pool = ManagerPool::new(download_pool); 30 | 31 | model_event_loop(socket.try_clone().unwrap(), ui_event_sender, file_server, manager_pool); 32 | 33 | start_daemon(socket.try_clone().unwrap()); 34 | 35 | send_ipmsg_br_entry(); 36 | } 37 | 38 | pub fn send_ipmsg_br_entry() { 39 | let packet = Packet::new(IPMSG_BR_ENTRY|IPMSG_BROADCASTOPT, Some(format!("{}\0\n{}", *protocol::HOST_NAME, *protocol::HOST_NAME))); 40 | GLOBLE_SENDER.send(ModelEvent::BroadcastEntry(packet)).unwrap(); 41 | } 42 | 43 | pub fn start_daemon(socket: UdpSocket){ 44 | let socket_clone = socket.try_clone().unwrap(); 45 | thread::spawn(move||{ 46 | loop { 47 | let mut buf = [0; 2048]; 48 | match socket_clone.recv_from(&mut buf) { 49 | Ok((amt, src)) => { 50 | //todo 默认是用中文编码 可配置化 51 | let receive_str = GB18030.decode(&buf[0..amt], DecoderTrap::Strict).unwrap(); 52 | info!("receive raw message -> {:?} from ip -> {:?}", receive_str, src.ip()); 53 | let result = packet_parser().parse(receive_str.as_str()); 54 | match result { 55 | Ok((mut packet, _)) => { 56 | packet.ip = src.ip().to_string(); 57 | GLOBLE_SENDER.send(ModelEvent::ReceivedPacket {packet}).unwrap(); 58 | } 59 | Err(_) => { 60 | error!("Invalid packet {} !", receive_str); 61 | } 62 | } 63 | }, 64 | Err(e) => { 65 | error!("couldn't recieve a datagram: {}", e); 66 | } 67 | } 68 | } 69 | }); 70 | } 71 | 72 | fn model_event_loop(socket: UdpSocket, ui_event_sender: Sender, file_server: FileServer, manager_pool: ManagerPool) { 73 | let socket_clone = socket.try_clone().unwrap(); 74 | thread::spawn(move || { 75 | while let Ok(ev) = GLOBLE_RECEIVER.recv() { 76 | match ev { 77 | ModelEvent::ReceivedPacket {packet} => { 78 | model_packet_dispatcher(packet); 79 | } 80 | ModelEvent::UserListSelected(text) => { 81 | ui_event_sender.try_send(UiEvent::UpdateUserListFooterStatus(text)).unwrap(); 82 | } 83 | ModelEvent::UserListDoubleClicked {name, ip} => { 84 | ui_event_sender.try_send(UiEvent::OpenOrReOpenChatWindow {name, ip}).unwrap(); 85 | } 86 | ModelEvent::BroadcastEntry(packet) => { 87 | socket_clone.set_broadcast(true).unwrap(); 88 | let addr:String = format!("{}:{}", IPMSG_LIMITED_BROADCAST, IPMSG_DEFAULT_PORT); 89 | socket_clone.send_to(packet.to_string().as_bytes(), addr.as_str()).expect("couldn't send message"); 90 | info!("send BroadcastEntry !"); 91 | } 92 | ModelEvent::RecMsgReply{packet, from_ip} => { 93 | let addr:String = format!("{}:{}", from_ip, protocol::IPMSG_DEFAULT_PORT); 94 | socket_clone.set_broadcast(false).unwrap(); 95 | socket_clone.send_to(packet.to_string().as_bytes(), addr.as_str()).expect("couldn't send message"); 96 | info!("send RecMsgReply !"); 97 | } 98 | ModelEvent::BroadcastExit(ip) => { 99 | ui_event_sender.try_send(UiEvent::UserListRemoveOne(ip)).unwrap(); 100 | } 101 | ModelEvent::RecOnlineMsgReply {packet, from_user} => { 102 | { 103 | let addr:String = format!("{}:{}", from_user.ip, protocol::IPMSG_DEFAULT_PORT); 104 | socket_clone.set_broadcast(false).unwrap(); 105 | socket_clone.send_to(packet.to_string().as_bytes(), addr.as_str()).expect("couldn't send message"); 106 | info!("send RecOnlineMsgReply ! {packet:?}"); 107 | } 108 | ui_event_sender.try_send(UiEvent::UserListAddOne(from_user)).unwrap(); 109 | } 110 | ModelEvent::ClickChatWindowCloseBtn{from_ip} => { 111 | ui_event_sender.try_send(UiEvent::CloseChatWindow(from_ip)).unwrap(); 112 | } 113 | ModelEvent::NotifyOnline {user} => { 114 | ui_event_sender.try_send(UiEvent::UserListAddOne(user)).unwrap(); 115 | } 116 | ModelEvent::ReceivedMsg {msg} => { 117 | let name = msg.clone().packet.unwrap().sender_name; 118 | let ip = msg.clone().ip.clone(); 119 | ui_event_sender.try_send(UiEvent::OpenOrReOpenChatWindow1 { name: name.clone(), ip: ip.clone(), packet: msg.clone().packet}).unwrap(); 120 | let additional_section = msg.clone().packet.unwrap().additional_section.unwrap(); 121 | let v: Vec<&str> = additional_section.split('\0').into_iter().collect(); 122 | ui_event_sender.try_send(UiEvent::DisplayReceivedMsgInHis{ 123 | from_ip: ip.clone(), 124 | name: name.clone(), 125 | context: v[0].to_owned(), 126 | files: msg.opt_files.unwrap_or(vec![]) 127 | }).unwrap(); 128 | } 129 | ModelEvent::SendOneMsg {to_ip, packet, context, files} => { 130 | let addr:String = format!("{}:{}", to_ip, protocol::IPMSG_DEFAULT_PORT); 131 | socket_clone.set_broadcast(false).unwrap(); 132 | socket_clone.send_to(crate::util::utf8_to_gb18030(packet.to_string().as_ref()).as_slice(), addr.as_str()).expect("couldn't send message"); 133 | info!("send SendOneMsg !"); 134 | ui_event_sender.try_send(UiEvent::DisplaySelfSendMsgInHis {to_ip, context, files: files.clone()}).unwrap(); 135 | { 136 | let mut file_pool = file_server.file_pool.lock().unwrap(); 137 | if let Some(file) = files { 138 | file_pool.push(file); 139 | } 140 | 141 | } 142 | } 143 | ModelEvent::DownloadIsBusy{ file } => { 144 | info!("{} is downloading!!!", file.name); 145 | } 146 | ModelEvent::PutDownloadTaskInPool {file, save_base_path, download_ip} => { 147 | manager_pool.clone().run(file, save_base_path, download_ip); 148 | } 149 | ModelEvent::RemoveDownloadTaskInPool {packet_id, file_id, download_ip } => { 150 | ui_event_sender.try_send(UiEvent::RemoveInReceivedList{ 151 | packet_id, 152 | file_id, 153 | download_ip 154 | }).unwrap(); 155 | } 156 | _ => { 157 | println!("{}", "aa"); 158 | } 159 | } 160 | } 161 | }); 162 | 163 | } 164 | 165 | fn model_packet_dispatcher(packet: Packet) { 166 | let mut extstr = String::new(); 167 | if let Some(ref additional_section) = (&packet).additional_section { 168 | extstr = additional_section.to_owned(); 169 | } 170 | let opt = protocol::get_opt((&packet).command_no); 171 | let cmd = protocol::get_mode((&packet).command_no); 172 | if opt& protocol::IPMSG_SENDCHECKOPT != 0 { 173 | let recvmsg = Packet::new(protocol::IPMSG_RECVMSG, Some((&packet).packet_no.to_string())); 174 | GLOBLE_SENDER.send(ModelEvent::RecMsgReply{packet: recvmsg, from_ip: (&packet).ip.to_owned()}); 175 | } 176 | if cmd == protocol::IPMSG_BR_EXIT {//收到下线通知消息 177 | GLOBLE_SENDER.send(ModelEvent::BroadcastExit((&packet).sender_host.to_owned())); 178 | }else if cmd == protocol::IPMSG_BR_ENTRY {//收到上线通知消息 179 | ///扩展段 用户名|用户组 180 | let ext_vec = extstr.splitn(2, |c| c == ':').collect::>(); 181 | let ansentry_packet = Packet::new(protocol::IPMSG_ANSENTRY, None); 182 | 183 | let group_name = if ext_vec.len() > 2 { 184 | ext_vec[1].to_owned() 185 | }else { 186 | "".to_owned() 187 | }; 188 | let user_name = if ext_vec.len() > 1&& !ext_vec[0].is_empty() { 189 | ext_vec[0].to_owned() 190 | }else { 191 | packet.sender_name.clone() 192 | }; 193 | 194 | let user = User::new(user_name, (&packet).sender_host.to_owned(), (&packet).ip.to_owned(), group_name); 195 | info!("{user:?}"); 196 | GLOBLE_SENDER.send(ModelEvent::RecOnlineMsgReply{packet: ansentry_packet, from_user: user}); 197 | }else if cmd == protocol::IPMSG_ANSENTRY {//通报新上线 198 | let user = User::new((&packet).sender_name.to_owned(), (&packet).sender_host.to_owned(), (&packet).ip.to_owned(), "".to_owned()); 199 | GLOBLE_SENDER.send(ModelEvent::NotifyOnline{user}); 200 | }else if cmd == protocol::IPMSG_SENDMSG {//收到发送的消息 201 | //文字消息|文件扩展段 202 | let ext_vec = extstr.split('\0').collect::>(); 203 | if opt& protocol::IPMSG_SECRETOPT != 0 {//是否是密封消息 204 | info!("i am secret message !"); 205 | } 206 | let msg_str = if ext_vec.len() > 0 { ext_vec[0] } else { "" }; 207 | //文字消息内容|文件扩展 208 | let mut files_opt: Option> = None; 209 | if opt& protocol::IPMSG_FILEATTACHOPT != 0 { 210 | if ext_vec.len() > 1 { 211 | let files_str: &str = ext_vec[1]; 212 | info!("i have file attachment {:?}", files_str); 213 | let files = files_str.split(protocol::FILELIST_SEPARATOR).into_iter().filter(|x: &&str| !x.is_empty()).collect::>(); 214 | let mut simple_file_infos = Vec::new(); 215 | for file_str in files { 216 | let file_attr = file_str.splitn(6, |c| c == ':').into_iter().filter(|x: &&str| !x.is_empty()).collect::>(); 217 | if file_attr.len() >= 5 { 218 | let file_id = file_attr[0].parse::().unwrap(); 219 | let file_name = file_attr[1]; 220 | let size = u64::from_str_radix(file_attr[2], 16).unwrap();//大小 221 | let mmtime = file_attr[3];//修改时间 222 | let mut mmtime_num = i64::from_str_radix(mmtime, 16).unwrap();//时间戳 223 | if mmtime_num >= 10000000000 { 224 | mmtime_num = (mmtime_num as i64)/1000; 225 | } 226 | let file_attr = file_attr[4].parse::().unwrap();//文件属性 227 | let ntime = NaiveDateTime::from_timestamp(mmtime_num, 0); 228 | if file_attr == protocol::IPMSG_FILE_REGULAR { 229 | info!("i am ipmsg_file_regular"); 230 | }else if file_attr == protocol::IPMSG_FILE_DIR { 231 | info!("i am ipmsg_file_dir"); 232 | }else { 233 | panic!("no no type") 234 | } 235 | let simple_file_info = ReceivedSimpleFileInfo { 236 | file_id, 237 | packet_id: (&packet).packet_no.parse::().unwrap(), 238 | name: file_name.to_owned(), 239 | attr: file_attr as u8, 240 | size, 241 | mtime: mmtime_num 242 | }; 243 | simple_file_infos.push(simple_file_info); 244 | } 245 | } 246 | if simple_file_infos.len() > 0 { 247 | files_opt = Some(simple_file_infos); 248 | } 249 | }; 250 | } 251 | let packet_clone = packet.clone(); 252 | let received_packet_inner = ReceivedPacketInner::new((&packet).ip.to_owned()).packet(packet_clone).option_opt_files(files_opt); 253 | GLOBLE_SENDER.send(ModelEvent::ReceivedMsg {msg: received_packet_inner}).unwrap(); 254 | }else if cmd == protocol::IPMSG_NOOPERATION { 255 | info!("i am IPMSG_NOOPERATION"); 256 | }else if cmd == protocol::IPMSG_BR_ABSENCE { 257 | info!("i am IPMSG_BR_ABSENCE"); 258 | }else { 259 | 260 | } 261 | } -------------------------------------------------------------------------------- /src/ui/main_win.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::net::UdpSocket; 3 | use async_channel::{Sender, Receiver}; 4 | use gtk::prelude::*; 5 | use gtk::{CellRendererPixbuf, CellRendererText, Label, ListStore, ScrolledWindow, TreeView, TreeViewColumn}; 6 | use log::info; 7 | use glib::clone; 8 | use crate::ui::chat_window::ChatWindow; 9 | use crate::events::model::model_run; 10 | use crate::models::event::ModelEvent; 11 | use crate::core::GLOBLE_SENDER; 12 | use crate::models::event::UiEvent; 13 | 14 | pub struct MainWindow {} 15 | 16 | impl MainWindow { 17 | pub fn new(application: &adw::Application) -> MainWindow { 18 | // let (tx, rx): (glib::Sender, glib::Receiver) = MainContext::channel::(Priority::HIGH); 19 | let (tx, rx): (Sender, Receiver) = async_channel::bounded(100); 20 | // let (model_sender, model_receiver): (crossbeam_channel::Sender, crossbeam_channel::Receiver) = unbounded(); 21 | 22 | let window = gtk::ApplicationWindow::new(application); 23 | window.set_title(Some("飞鸽传书")); 24 | // window.set_position(gtk::WindowPosition::Center); 25 | window.set_default_size(200, 500); 26 | window.set_resizable(false); 27 | /*window.connect_delete_event(clone!(@weak window => @default-return Inhibit(false), move |_, _| { 28 | unsafe { 29 | &window.destroy(); 30 | } 31 | return Inhibit(false); 32 | }));*/ 33 | window.connect_close_request(clone!(@strong tx => @default-return glib::signal::Inhibit(false), move |win| { 34 | if let Some(application) = win.application() { 35 | application.remove_window(win); 36 | } 37 | glib::Propagation::Stop 38 | })); 39 | 40 | //纵向 41 | let v_box = gtk::Box::new(gtk::Orientation::Vertical, 0); 42 | 43 | let label = Label::new(Option::from("")); 44 | let scrolled = ScrolledWindow::new();//None::<>k::Adjustment>, None::<>k::Adjustment> 45 | scrolled.set_policy(gtk::PolicyType::Automatic, gtk::PolicyType::Automatic); 46 | let tree = create_and_setup_view(); 47 | let model = create_and_fill_model(); 48 | tree.set_model(Some(&model)); 49 | scrolled.set_child(Some(&tree)); 50 | scrolled.set_min_content_height(450); 51 | // v_box.add(&menu_bar); 52 | v_box.append(&scrolled); 53 | v_box.append(&label); 54 | GLOBLE_SENDER.send(ModelEvent::UserListSelected(String::from("未选择"))).unwrap(); 55 | 56 | tree.connect_cursor_changed( move |tree_view| { 57 | let selection = tree_view.selection(); 58 | if let Some((model, iter)) = selection.selected() { 59 | let str1 = model.get_value(&iter, 1).get::().unwrap(); 60 | GLOBLE_SENDER.send(ModelEvent::UserListSelected(str1)).unwrap(); 61 | } 62 | }); 63 | 64 | let mut chat_windows: HashMap = HashMap::new(); 65 | 66 | tree.connect_row_activated(move |tree_view, tree_path, tree_view_column| { 67 | let selection = tree_view.selection(); 68 | if let Some((model, iter)) = selection.selected() { 69 | let ip_str = model.get_value(&iter, 4).get::().unwrap(); 70 | let name = model.get_value(&iter, 1).get::().unwrap(); 71 | GLOBLE_SENDER.send(ModelEvent::UserListDoubleClicked{name, ip: ip_str }).unwrap(); 72 | } 73 | }); 74 | 75 | let socket: UdpSocket = match UdpSocket::bind(crate::constants::protocol::ADDR.as_str()) { 76 | Ok(s) => { 77 | info!("udp server start listening! {:?}", crate::constants::protocol::ADDR.as_str()); 78 | s 79 | } 80 | Err(e) => panic!("couldn't bind socket: {}", e) 81 | }; 82 | 83 | model_run(socket.try_clone().unwrap(), tx); 84 | 85 | glib::spawn_future_local(async move { 86 | while let Ok(event) = rx.recv().await { 87 | match event { 88 | UiEvent::OpenOrReOpenChatWindow { name, ip } => { 89 | info!("winds 的长度 {:?}", chat_windows.len()); 90 | match chat_windows.get(&ip) { 91 | Some(win) => {} 92 | None => { 93 | let chat_win = crate::ui::chat_window::create_chat_window(name, ip.clone()); 94 | chat_windows.insert(ip.clone(), chat_win); 95 | } 96 | } 97 | } 98 | UiEvent::UpdateUserListFooterStatus(text) => { 99 | label.set_text(&format!("-- {} --", text)); 100 | } 101 | UiEvent::UserListRemoveOne(ip) => { 102 | if let Some(first) = model.iter_first() {//拿出来第一条 103 | let mut num: u32 = model.string_from_iter(&first).unwrap().parse::().unwrap();//序号 会改变 104 | let ip1 = model.get_value(&first, 4).get::().unwrap();//获取ip 105 | if ip == ip1 { 106 | model.remove(&first); 107 | } else { 108 | loop { 109 | num = num + 1; 110 | if let Some(next_iter) = model.iter_from_string(&num.to_string()) { 111 | let next_ip = model.get_value(&next_iter, 4).get::().unwrap();//获取ip 112 | if next_ip == ip1 { 113 | model.remove(&next_iter); 114 | break; 115 | } 116 | } else { 117 | break; 118 | } 119 | } 120 | } 121 | } 122 | } 123 | UiEvent::UserListAddOne(income_user) => { 124 | let mut in_flag = false; 125 | if let Some(first) = model.iter_first() {//拿出来第一条 126 | let mut num: u32 = model.string_from_iter(&first).unwrap().parse::().unwrap();//序号 会改变 127 | let ip = model.get_value(&first, 4).get::().unwrap();//获取ip 128 | if ip == income_user.ip { 129 | in_flag = true; 130 | } else { 131 | loop { 132 | num = num + 1; 133 | if let Some(next_iter) = model.iter_from_string(&num.to_string()) { 134 | let next_ip = model.get_value(&next_iter, 4).get::().unwrap();//获取ip 135 | if next_ip == income_user.ip { 136 | in_flag = true; 137 | break; 138 | } 139 | } else { 140 | break; 141 | } 142 | } 143 | } 144 | } 145 | if !in_flag { 146 | //model.insert_with_values(None, &[0, 1, 2, 3], &[&&income_user.name, &&income_user.group, &&income_user.host, &&income_user.ip]); 147 | model.insert_with_values(None, &[(0, &"face-smile"), (1, &&income_user.name), (2, &&income_user.group), (3, &&income_user.host), (4, &&income_user.ip)]); 148 | } 149 | } 150 | UiEvent::CloseChatWindow(ip) => { 151 | if let Some(win) = chat_windows.get(&ip) { 152 | chat_windows.remove(&ip); 153 | } 154 | } 155 | UiEvent::OpenOrReOpenChatWindow1 { name, ip, packet } => { 156 | match chat_windows.get(&ip) { 157 | Some(win) => { 158 | win.win.show(); 159 | } 160 | None => { 161 | let chat_win = crate::ui::chat_window::create_chat_window(name, ip.clone()); 162 | chat_win.win.show(); 163 | chat_windows.insert(ip.clone(), chat_win); 164 | 165 | } 166 | } 167 | } 168 | UiEvent::DisplaySelfSendMsgInHis { to_ip, context, files } => { 169 | match chat_windows.get(&to_ip) { 170 | Some(win) => { 171 | let (his_start_iter, mut his_end_iter) = win.his_view.buffer().bounds(); 172 | win.his_view.buffer().insert(&mut his_end_iter, format!("{}:{}\n", "我", context).as_str()); 173 | } 174 | None => {} 175 | } 176 | } 177 | UiEvent::DisplayReceivedMsgInHis { from_ip, name, context, files } => { 178 | match chat_windows.get(&from_ip) { 179 | Some(win) => { 180 | let (his_start_iter, mut his_end_iter) = win.his_view.buffer().bounds(); 181 | win.his_view.buffer().insert(&mut his_end_iter, format!("{}:{}\n", name, context).as_str()); 182 | 183 | for file in &files { 184 | //win.received_store.insert_with_values(None, &[0, 1, 2, 3, 4, 5], &[&&file.name, &&file.file_id, &&file.packet_id, &&file.attr, &&file.size, &&file.mtime]); 185 | win.received_store.insert_with_values(None, &[(0, &&file.name), (1, &&file.file_id), (2, &&file.packet_id), (3, &&file.attr), (4, &&file.size), (5, &&file.mtime)]); 186 | } 187 | } 188 | None => {} 189 | } 190 | } 191 | UiEvent::RemoveInReceivedList { packet_id, file_id, download_ip } => { 192 | match chat_windows.get(&download_ip) { 193 | Some(win) => { 194 | let pre_receive_file_store = &win.received_store; 195 | if let Some(first) = pre_receive_file_store.iter_first() { 196 | let mut num: u32 = pre_receive_file_store.string_from_iter(&first).unwrap().parse::().unwrap();//序号 会改变 197 | let received_file_id = pre_receive_file_store.get_value(&first, 1).get::().unwrap(); 198 | let received_packet_id = pre_receive_file_store.get_value(&first, 2).get::().unwrap(); 199 | if file_id == received_file_id && packet_id == received_packet_id { 200 | pre_receive_file_store.remove(&first); 201 | } else { 202 | loop { 203 | num = num + 1; 204 | if let Some(next_iter) = pre_receive_file_store.iter_from_string(&num.to_string()) { 205 | let next_file_id = pre_receive_file_store.get_value(&next_iter, 1).get::().unwrap(); 206 | let next_packet_id = pre_receive_file_store.get_value(&next_iter, 2).get::().unwrap(); 207 | if next_file_id == file_id && next_packet_id == packet_id { 208 | pre_receive_file_store.remove(&next_iter); 209 | break; 210 | } 211 | } else { 212 | break; 213 | } 214 | } 215 | } 216 | } 217 | } 218 | None => {} 219 | } 220 | } 221 | _ => { 222 | println!("{}", "aaa"); 223 | } 224 | }; 225 | } 226 | }); 227 | window.set_child(Some(&v_box)); 228 | window.present(); 229 | MainWindow {} 230 | } 231 | } 232 | 233 | fn create_and_setup_view() -> TreeView { 234 | // Creating the tree view. 235 | let tree = TreeView::new(); 236 | { 237 | let renderer = CellRendererPixbuf::new(); 238 | let cell = CellRendererText::new(); 239 | let col = TreeViewColumn::new(); 240 | col.pack_start(&renderer, false); 241 | col.pack_end(&cell, false); 242 | // renderer.set_icon_name(Some("face-smile")); 243 | col.add_attribute(&renderer, "icon-name", 0); 244 | col.add_attribute(&cell, "text", 1); 245 | tree.append_column(&col); 246 | } 247 | tree.set_headers_visible(false); 248 | tree 249 | } 250 | 251 | fn create_and_fill_model() -> ListStore { 252 | // Creation of a model with two rows. 253 | let model = ListStore::new(&[String::static_type(), String::static_type(), String::static_type(), String::static_type(), String::static_type()]); 254 | model 255 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "anstream" 46 | version = "0.6.5" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" 49 | dependencies = [ 50 | "anstyle", 51 | "anstyle-parse", 52 | "anstyle-query", 53 | "anstyle-wincon", 54 | "colorchoice", 55 | "utf8parse", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle" 60 | version = "1.0.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" 63 | 64 | [[package]] 65 | name = "anstyle-parse" 66 | version = "0.2.3" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 69 | dependencies = [ 70 | "utf8parse", 71 | ] 72 | 73 | [[package]] 74 | name = "anstyle-query" 75 | version = "1.0.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 78 | dependencies = [ 79 | "windows-sys 0.52.0", 80 | ] 81 | 82 | [[package]] 83 | name = "anstyle-wincon" 84 | version = "3.0.2" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 87 | dependencies = [ 88 | "anstyle", 89 | "windows-sys 0.52.0", 90 | ] 91 | 92 | [[package]] 93 | name = "anyhow" 94 | version = "1.0.79" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" 97 | 98 | [[package]] 99 | name = "async-channel" 100 | version = "2.1.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" 103 | dependencies = [ 104 | "concurrent-queue", 105 | "event-listener", 106 | "event-listener-strategy", 107 | "futures-core", 108 | "pin-project-lite", 109 | ] 110 | 111 | [[package]] 112 | name = "autocfg" 113 | version = "1.1.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 116 | 117 | [[package]] 118 | name = "backtrace" 119 | version = "0.3.69" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 122 | dependencies = [ 123 | "addr2line", 124 | "cc", 125 | "cfg-if", 126 | "libc", 127 | "miniz_oxide", 128 | "object", 129 | "rustc-demangle", 130 | ] 131 | 132 | [[package]] 133 | name = "bitflags" 134 | version = "2.4.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 137 | 138 | [[package]] 139 | name = "bumpalo" 140 | version = "3.14.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 143 | 144 | [[package]] 145 | name = "byteorder" 146 | version = "1.5.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 149 | 150 | [[package]] 151 | name = "bytes" 152 | version = "1.5.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 155 | 156 | [[package]] 157 | name = "cairo-rs" 158 | version = "0.18.5" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" 161 | dependencies = [ 162 | "bitflags", 163 | "cairo-sys-rs", 164 | "glib", 165 | "libc", 166 | "once_cell", 167 | "thiserror", 168 | ] 169 | 170 | [[package]] 171 | name = "cairo-sys-rs" 172 | version = "0.18.2" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" 175 | dependencies = [ 176 | "glib-sys", 177 | "libc", 178 | "system-deps", 179 | ] 180 | 181 | [[package]] 182 | name = "cc" 183 | version = "1.0.83" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 186 | dependencies = [ 187 | "libc", 188 | ] 189 | 190 | [[package]] 191 | name = "cfg-expr" 192 | version = "0.15.5" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" 195 | dependencies = [ 196 | "smallvec", 197 | "target-lexicon", 198 | ] 199 | 200 | [[package]] 201 | name = "cfg-if" 202 | version = "1.0.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 205 | 206 | [[package]] 207 | name = "chrono" 208 | version = "0.4.31" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" 211 | dependencies = [ 212 | "android-tzdata", 213 | "iana-time-zone", 214 | "js-sys", 215 | "num-traits", 216 | "wasm-bindgen", 217 | "windows-targets 0.48.5", 218 | ] 219 | 220 | [[package]] 221 | name = "colorchoice" 222 | version = "1.0.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 225 | 226 | [[package]] 227 | name = "combine" 228 | version = "4.6.6" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" 231 | dependencies = [ 232 | "bytes", 233 | "memchr", 234 | ] 235 | 236 | [[package]] 237 | name = "concurrent-queue" 238 | version = "2.4.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" 241 | dependencies = [ 242 | "crossbeam-utils", 243 | ] 244 | 245 | [[package]] 246 | name = "core-foundation-sys" 247 | version = "0.8.6" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 250 | 251 | [[package]] 252 | name = "crossbeam-channel" 253 | version = "0.5.10" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" 256 | dependencies = [ 257 | "cfg-if", 258 | "crossbeam-utils", 259 | ] 260 | 261 | [[package]] 262 | name = "crossbeam-utils" 263 | version = "0.8.18" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" 266 | dependencies = [ 267 | "cfg-if", 268 | ] 269 | 270 | [[package]] 271 | name = "either" 272 | version = "1.9.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 275 | 276 | [[package]] 277 | name = "encoding" 278 | version = "0.2.33" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" 281 | dependencies = [ 282 | "encoding-index-japanese", 283 | "encoding-index-korean", 284 | "encoding-index-simpchinese", 285 | "encoding-index-singlebyte", 286 | "encoding-index-tradchinese", 287 | ] 288 | 289 | [[package]] 290 | name = "encoding-index-japanese" 291 | version = "1.20141219.5" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" 294 | dependencies = [ 295 | "encoding_index_tests", 296 | ] 297 | 298 | [[package]] 299 | name = "encoding-index-korean" 300 | version = "1.20141219.5" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" 303 | dependencies = [ 304 | "encoding_index_tests", 305 | ] 306 | 307 | [[package]] 308 | name = "encoding-index-simpchinese" 309 | version = "1.20141219.5" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" 312 | dependencies = [ 313 | "encoding_index_tests", 314 | ] 315 | 316 | [[package]] 317 | name = "encoding-index-singlebyte" 318 | version = "1.20141219.5" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" 321 | dependencies = [ 322 | "encoding_index_tests", 323 | ] 324 | 325 | [[package]] 326 | name = "encoding-index-tradchinese" 327 | version = "1.20141219.5" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" 330 | dependencies = [ 331 | "encoding_index_tests", 332 | ] 333 | 334 | [[package]] 335 | name = "encoding_index_tests" 336 | version = "0.1.4" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" 339 | 340 | [[package]] 341 | name = "env_logger" 342 | version = "0.10.1" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" 345 | dependencies = [ 346 | "humantime", 347 | "is-terminal", 348 | "log", 349 | "regex", 350 | "termcolor", 351 | ] 352 | 353 | [[package]] 354 | name = "equivalent" 355 | version = "1.0.1" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 358 | 359 | [[package]] 360 | name = "errno" 361 | version = "0.3.8" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 364 | dependencies = [ 365 | "libc", 366 | "windows-sys 0.52.0", 367 | ] 368 | 369 | [[package]] 370 | name = "event-listener" 371 | version = "4.0.2" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "218a870470cce1469024e9fb66b901aa983929d81304a1cdb299f28118e550d5" 374 | dependencies = [ 375 | "concurrent-queue", 376 | "parking", 377 | "pin-project-lite", 378 | ] 379 | 380 | [[package]] 381 | name = "event-listener-strategy" 382 | version = "0.4.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" 385 | dependencies = [ 386 | "event-listener", 387 | "pin-project-lite", 388 | ] 389 | 390 | [[package]] 391 | name = "field-offset" 392 | version = "0.3.6" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" 395 | dependencies = [ 396 | "memoffset", 397 | "rustc_version", 398 | ] 399 | 400 | [[package]] 401 | name = "futures-channel" 402 | version = "0.3.30" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 405 | dependencies = [ 406 | "futures-core", 407 | ] 408 | 409 | [[package]] 410 | name = "futures-core" 411 | version = "0.3.30" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 414 | 415 | [[package]] 416 | name = "futures-executor" 417 | version = "0.3.30" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 420 | dependencies = [ 421 | "futures-core", 422 | "futures-task", 423 | "futures-util", 424 | ] 425 | 426 | [[package]] 427 | name = "futures-io" 428 | version = "0.3.30" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 431 | 432 | [[package]] 433 | name = "futures-macro" 434 | version = "0.3.30" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 437 | dependencies = [ 438 | "proc-macro2", 439 | "quote", 440 | "syn 2.0.45", 441 | ] 442 | 443 | [[package]] 444 | name = "futures-task" 445 | version = "0.3.30" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 448 | 449 | [[package]] 450 | name = "futures-util" 451 | version = "0.3.30" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 454 | dependencies = [ 455 | "futures-core", 456 | "futures-macro", 457 | "futures-task", 458 | "pin-project-lite", 459 | "pin-utils", 460 | "slab", 461 | ] 462 | 463 | [[package]] 464 | name = "gdk-pixbuf" 465 | version = "0.18.5" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" 468 | dependencies = [ 469 | "gdk-pixbuf-sys", 470 | "gio", 471 | "glib", 472 | "libc", 473 | "once_cell", 474 | ] 475 | 476 | [[package]] 477 | name = "gdk-pixbuf-sys" 478 | version = "0.18.0" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" 481 | dependencies = [ 482 | "gio-sys", 483 | "glib-sys", 484 | "gobject-sys", 485 | "libc", 486 | "system-deps", 487 | ] 488 | 489 | [[package]] 490 | name = "gdk4" 491 | version = "0.7.3" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "7edb019ad581f8ecf8ea8e4baa6df7c483a95b5a59be3140be6a9c3b0c632af6" 494 | dependencies = [ 495 | "cairo-rs", 496 | "gdk-pixbuf", 497 | "gdk4-sys", 498 | "gio", 499 | "glib", 500 | "libc", 501 | "pango", 502 | ] 503 | 504 | [[package]] 505 | name = "gdk4-sys" 506 | version = "0.7.2" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "dbab43f332a3cf1df9974da690b5bb0e26720ed09a228178ce52175372dcfef0" 509 | dependencies = [ 510 | "cairo-sys-rs", 511 | "gdk-pixbuf-sys", 512 | "gio-sys", 513 | "glib-sys", 514 | "gobject-sys", 515 | "libc", 516 | "pango-sys", 517 | "pkg-config", 518 | "system-deps", 519 | ] 520 | 521 | [[package]] 522 | name = "getrandom" 523 | version = "0.2.11" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 526 | dependencies = [ 527 | "cfg-if", 528 | "libc", 529 | "wasi", 530 | ] 531 | 532 | [[package]] 533 | name = "gimli" 534 | version = "0.28.1" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 537 | 538 | [[package]] 539 | name = "gio" 540 | version = "0.18.4" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" 543 | dependencies = [ 544 | "futures-channel", 545 | "futures-core", 546 | "futures-io", 547 | "futures-util", 548 | "gio-sys", 549 | "glib", 550 | "libc", 551 | "once_cell", 552 | "pin-project-lite", 553 | "smallvec", 554 | "thiserror", 555 | ] 556 | 557 | [[package]] 558 | name = "gio-sys" 559 | version = "0.18.1" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" 562 | dependencies = [ 563 | "glib-sys", 564 | "gobject-sys", 565 | "libc", 566 | "system-deps", 567 | "winapi", 568 | ] 569 | 570 | [[package]] 571 | name = "glib" 572 | version = "0.18.5" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" 575 | dependencies = [ 576 | "bitflags", 577 | "futures-channel", 578 | "futures-core", 579 | "futures-executor", 580 | "futures-task", 581 | "futures-util", 582 | "gio-sys", 583 | "glib-macros", 584 | "glib-sys", 585 | "gobject-sys", 586 | "libc", 587 | "memchr", 588 | "once_cell", 589 | "smallvec", 590 | "thiserror", 591 | ] 592 | 593 | [[package]] 594 | name = "glib-macros" 595 | version = "0.18.5" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" 598 | dependencies = [ 599 | "heck", 600 | "proc-macro-crate 2.0.1", 601 | "proc-macro-error", 602 | "proc-macro2", 603 | "quote", 604 | "syn 2.0.45", 605 | ] 606 | 607 | [[package]] 608 | name = "glib-sys" 609 | version = "0.18.1" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" 612 | dependencies = [ 613 | "libc", 614 | "system-deps", 615 | ] 616 | 617 | [[package]] 618 | name = "gobject-sys" 619 | version = "0.18.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" 622 | dependencies = [ 623 | "glib-sys", 624 | "libc", 625 | "system-deps", 626 | ] 627 | 628 | [[package]] 629 | name = "graphene-rs" 630 | version = "0.18.1" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "3b2228cda1505613a7a956cca69076892cfbda84fc2b7a62b94a41a272c0c401" 633 | dependencies = [ 634 | "glib", 635 | "graphene-sys", 636 | "libc", 637 | ] 638 | 639 | [[package]] 640 | name = "graphene-sys" 641 | version = "0.18.1" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "cc4144cee8fc8788f2a9b73dc5f1d4e1189d1f95305c4cb7bd9c1af1cfa31f59" 644 | dependencies = [ 645 | "glib-sys", 646 | "libc", 647 | "pkg-config", 648 | "system-deps", 649 | ] 650 | 651 | [[package]] 652 | name = "gsk4" 653 | version = "0.7.3" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "0d958e351d2f210309b32d081c832d7de0aca0b077aa10d88336c6379bd01f7e" 656 | dependencies = [ 657 | "cairo-rs", 658 | "gdk4", 659 | "glib", 660 | "graphene-rs", 661 | "gsk4-sys", 662 | "libc", 663 | "pango", 664 | ] 665 | 666 | [[package]] 667 | name = "gsk4-sys" 668 | version = "0.7.3" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "12bd9e3effea989f020e8f1ff3fa3b8c63ba93d43b899c11a118868853a56d55" 671 | dependencies = [ 672 | "cairo-sys-rs", 673 | "gdk4-sys", 674 | "glib-sys", 675 | "gobject-sys", 676 | "graphene-sys", 677 | "libc", 678 | "pango-sys", 679 | "system-deps", 680 | ] 681 | 682 | [[package]] 683 | name = "gtk4" 684 | version = "0.7.3" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "5aeb51aa3e9728575a053e1f43543cd9992ac2477e1b186ad824fd4adfb70842" 687 | dependencies = [ 688 | "cairo-rs", 689 | "field-offset", 690 | "futures-channel", 691 | "gdk-pixbuf", 692 | "gdk4", 693 | "gio", 694 | "glib", 695 | "graphene-rs", 696 | "gsk4", 697 | "gtk4-macros", 698 | "gtk4-sys", 699 | "libc", 700 | "pango", 701 | ] 702 | 703 | [[package]] 704 | name = "gtk4-macros" 705 | version = "0.7.2" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "d57ec49cf9b657f69a05bca8027cff0a8dfd0c49e812be026fc7311f2163832f" 708 | dependencies = [ 709 | "anyhow", 710 | "proc-macro-crate 1.3.1", 711 | "proc-macro-error", 712 | "proc-macro2", 713 | "quote", 714 | "syn 1.0.109", 715 | ] 716 | 717 | [[package]] 718 | name = "gtk4-sys" 719 | version = "0.7.3" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "54d8c4aa23638ce9faa2caf7e2a27d4a1295af2155c8e8d28c4d4eeca7a65eb8" 722 | dependencies = [ 723 | "cairo-sys-rs", 724 | "gdk-pixbuf-sys", 725 | "gdk4-sys", 726 | "gio-sys", 727 | "glib-sys", 728 | "gobject-sys", 729 | "graphene-sys", 730 | "gsk4-sys", 731 | "libc", 732 | "pango-sys", 733 | "system-deps", 734 | ] 735 | 736 | [[package]] 737 | name = "hashbrown" 738 | version = "0.14.3" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 741 | 742 | [[package]] 743 | name = "heck" 744 | version = "0.4.1" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 747 | 748 | [[package]] 749 | name = "hermit-abi" 750 | version = "0.3.3" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 753 | 754 | [[package]] 755 | name = "hostname" 756 | version = "0.3.1" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" 759 | dependencies = [ 760 | "libc", 761 | "match_cfg", 762 | "winapi", 763 | ] 764 | 765 | [[package]] 766 | name = "human-panic" 767 | version = "1.2.1" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "b82da652938b83f94cfdaaf9ae7aaadb8430d84b0dfda226998416318727eac2" 770 | dependencies = [ 771 | "anstream", 772 | "anstyle", 773 | "backtrace", 774 | "os_info", 775 | "serde", 776 | "serde_derive", 777 | "toml 0.7.8", 778 | "uuid", 779 | ] 780 | 781 | [[package]] 782 | name = "humantime" 783 | version = "2.1.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 786 | 787 | [[package]] 788 | name = "iana-time-zone" 789 | version = "0.1.59" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" 792 | dependencies = [ 793 | "android_system_properties", 794 | "core-foundation-sys", 795 | "iana-time-zone-haiku", 796 | "js-sys", 797 | "wasm-bindgen", 798 | "windows-core", 799 | ] 800 | 801 | [[package]] 802 | name = "iana-time-zone-haiku" 803 | version = "0.1.2" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 806 | dependencies = [ 807 | "cc", 808 | ] 809 | 810 | [[package]] 811 | name = "indexmap" 812 | version = "2.1.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 815 | dependencies = [ 816 | "equivalent", 817 | "hashbrown", 818 | ] 819 | 820 | [[package]] 821 | name = "ipmsg-rs" 822 | version = "0.7.1" 823 | dependencies = [ 824 | "anyhow", 825 | "async-channel", 826 | "chrono", 827 | "combine", 828 | "crossbeam-channel", 829 | "encoding", 830 | "env_logger", 831 | "gdk-pixbuf", 832 | "gdk4", 833 | "gio", 834 | "glib", 835 | "gtk4", 836 | "hostname", 837 | "human-panic", 838 | "libadwaita", 839 | "local-ip-address", 840 | "log", 841 | "once_cell", 842 | "pango", 843 | "thiserror", 844 | ] 845 | 846 | [[package]] 847 | name = "is-terminal" 848 | version = "0.4.10" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" 851 | dependencies = [ 852 | "hermit-abi", 853 | "rustix", 854 | "windows-sys 0.52.0", 855 | ] 856 | 857 | [[package]] 858 | name = "js-sys" 859 | version = "0.3.66" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" 862 | dependencies = [ 863 | "wasm-bindgen", 864 | ] 865 | 866 | [[package]] 867 | name = "libadwaita" 868 | version = "0.5.3" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "2fe7e70c06507ed10a16cda707f358fbe60fe0dc237498f78c686ade92fd979c" 871 | dependencies = [ 872 | "gdk-pixbuf", 873 | "gdk4", 874 | "gio", 875 | "glib", 876 | "gtk4", 877 | "libadwaita-sys", 878 | "libc", 879 | "pango", 880 | ] 881 | 882 | [[package]] 883 | name = "libadwaita-sys" 884 | version = "0.5.3" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "5e10aaa38de1d53374f90deeb4535209adc40cc5dba37f9704724169bceec69a" 887 | dependencies = [ 888 | "gdk4-sys", 889 | "gio-sys", 890 | "glib-sys", 891 | "gobject-sys", 892 | "gtk4-sys", 893 | "libc", 894 | "pango-sys", 895 | "system-deps", 896 | ] 897 | 898 | [[package]] 899 | name = "libc" 900 | version = "0.2.151" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" 903 | 904 | [[package]] 905 | name = "linux-raw-sys" 906 | version = "0.4.12" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" 909 | 910 | [[package]] 911 | name = "local-ip-address" 912 | version = "0.5.6" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "66357e687a569abca487dc399a9c9ac19beb3f13991ed49f00c144e02cbd42ab" 915 | dependencies = [ 916 | "libc", 917 | "neli", 918 | "thiserror", 919 | "windows-sys 0.48.0", 920 | ] 921 | 922 | [[package]] 923 | name = "log" 924 | version = "0.4.20" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 927 | 928 | [[package]] 929 | name = "match_cfg" 930 | version = "0.1.0" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" 933 | 934 | [[package]] 935 | name = "memchr" 936 | version = "2.7.1" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 939 | 940 | [[package]] 941 | name = "memoffset" 942 | version = "0.9.0" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 945 | dependencies = [ 946 | "autocfg", 947 | ] 948 | 949 | [[package]] 950 | name = "miniz_oxide" 951 | version = "0.7.1" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 954 | dependencies = [ 955 | "adler", 956 | ] 957 | 958 | [[package]] 959 | name = "neli" 960 | version = "0.6.4" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43" 963 | dependencies = [ 964 | "byteorder", 965 | "libc", 966 | "log", 967 | "neli-proc-macros", 968 | ] 969 | 970 | [[package]] 971 | name = "neli-proc-macros" 972 | version = "0.1.3" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" 975 | dependencies = [ 976 | "either", 977 | "proc-macro2", 978 | "quote", 979 | "serde", 980 | "syn 1.0.109", 981 | ] 982 | 983 | [[package]] 984 | name = "num-traits" 985 | version = "0.2.17" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 988 | dependencies = [ 989 | "autocfg", 990 | ] 991 | 992 | [[package]] 993 | name = "object" 994 | version = "0.32.2" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 997 | dependencies = [ 998 | "memchr", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "once_cell" 1003 | version = "1.19.0" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1006 | 1007 | [[package]] 1008 | name = "os_info" 1009 | version = "3.7.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" 1012 | dependencies = [ 1013 | "log", 1014 | "serde", 1015 | "winapi", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "pango" 1020 | version = "0.18.3" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" 1023 | dependencies = [ 1024 | "gio", 1025 | "glib", 1026 | "libc", 1027 | "once_cell", 1028 | "pango-sys", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "pango-sys" 1033 | version = "0.18.0" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" 1036 | dependencies = [ 1037 | "glib-sys", 1038 | "gobject-sys", 1039 | "libc", 1040 | "system-deps", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "parking" 1045 | version = "2.2.0" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" 1048 | 1049 | [[package]] 1050 | name = "pin-project-lite" 1051 | version = "0.2.13" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 1054 | 1055 | [[package]] 1056 | name = "pin-utils" 1057 | version = "0.1.0" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1060 | 1061 | [[package]] 1062 | name = "pkg-config" 1063 | version = "0.3.28" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" 1066 | 1067 | [[package]] 1068 | name = "proc-macro-crate" 1069 | version = "1.3.1" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 1072 | dependencies = [ 1073 | "once_cell", 1074 | "toml_edit 0.19.15", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "proc-macro-crate" 1079 | version = "2.0.1" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" 1082 | dependencies = [ 1083 | "toml_datetime", 1084 | "toml_edit 0.20.2", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "proc-macro-error" 1089 | version = "1.0.4" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1092 | dependencies = [ 1093 | "proc-macro-error-attr", 1094 | "proc-macro2", 1095 | "quote", 1096 | "syn 1.0.109", 1097 | "version_check", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "proc-macro-error-attr" 1102 | version = "1.0.4" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1105 | dependencies = [ 1106 | "proc-macro2", 1107 | "quote", 1108 | "version_check", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "proc-macro2" 1113 | version = "1.0.74" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" 1116 | dependencies = [ 1117 | "unicode-ident", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "quote" 1122 | version = "1.0.34" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a" 1125 | dependencies = [ 1126 | "proc-macro2", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "regex" 1131 | version = "1.10.2" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 1134 | dependencies = [ 1135 | "aho-corasick", 1136 | "memchr", 1137 | "regex-automata", 1138 | "regex-syntax", 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "regex-automata" 1143 | version = "0.4.3" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 1146 | dependencies = [ 1147 | "aho-corasick", 1148 | "memchr", 1149 | "regex-syntax", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "regex-syntax" 1154 | version = "0.8.2" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 1157 | 1158 | [[package]] 1159 | name = "rustc-demangle" 1160 | version = "0.1.23" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1163 | 1164 | [[package]] 1165 | name = "rustc_version" 1166 | version = "0.4.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1169 | dependencies = [ 1170 | "semver", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "rustix" 1175 | version = "0.38.28" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" 1178 | dependencies = [ 1179 | "bitflags", 1180 | "errno", 1181 | "libc", 1182 | "linux-raw-sys", 1183 | "windows-sys 0.52.0", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "semver" 1188 | version = "1.0.20" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" 1191 | 1192 | [[package]] 1193 | name = "serde" 1194 | version = "1.0.193" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" 1197 | dependencies = [ 1198 | "serde_derive", 1199 | ] 1200 | 1201 | [[package]] 1202 | name = "serde_derive" 1203 | version = "1.0.193" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" 1206 | dependencies = [ 1207 | "proc-macro2", 1208 | "quote", 1209 | "syn 2.0.45", 1210 | ] 1211 | 1212 | [[package]] 1213 | name = "serde_spanned" 1214 | version = "0.6.5" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 1217 | dependencies = [ 1218 | "serde", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "slab" 1223 | version = "0.4.9" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1226 | dependencies = [ 1227 | "autocfg", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "smallvec" 1232 | version = "1.11.2" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" 1235 | 1236 | [[package]] 1237 | name = "syn" 1238 | version = "1.0.109" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1241 | dependencies = [ 1242 | "proc-macro2", 1243 | "quote", 1244 | "unicode-ident", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "syn" 1249 | version = "2.0.45" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "0eae3c679c56dc214320b67a1bc04ef3dfbd6411f6443974b5e4893231298e66" 1252 | dependencies = [ 1253 | "proc-macro2", 1254 | "quote", 1255 | "unicode-ident", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "system-deps" 1260 | version = "6.2.0" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" 1263 | dependencies = [ 1264 | "cfg-expr", 1265 | "heck", 1266 | "pkg-config", 1267 | "toml 0.8.2", 1268 | "version-compare", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "target-lexicon" 1273 | version = "0.12.12" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" 1276 | 1277 | [[package]] 1278 | name = "termcolor" 1279 | version = "1.4.0" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" 1282 | dependencies = [ 1283 | "winapi-util", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "thiserror" 1288 | version = "1.0.55" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" 1291 | dependencies = [ 1292 | "thiserror-impl", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "thiserror-impl" 1297 | version = "1.0.55" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" 1300 | dependencies = [ 1301 | "proc-macro2", 1302 | "quote", 1303 | "syn 2.0.45", 1304 | ] 1305 | 1306 | [[package]] 1307 | name = "toml" 1308 | version = "0.7.8" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 1311 | dependencies = [ 1312 | "serde", 1313 | "serde_spanned", 1314 | "toml_datetime", 1315 | "toml_edit 0.19.15", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "toml" 1320 | version = "0.8.2" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" 1323 | dependencies = [ 1324 | "serde", 1325 | "serde_spanned", 1326 | "toml_datetime", 1327 | "toml_edit 0.20.2", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "toml_datetime" 1332 | version = "0.6.3" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 1335 | dependencies = [ 1336 | "serde", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "toml_edit" 1341 | version = "0.19.15" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 1344 | dependencies = [ 1345 | "indexmap", 1346 | "serde", 1347 | "serde_spanned", 1348 | "toml_datetime", 1349 | "winnow", 1350 | ] 1351 | 1352 | [[package]] 1353 | name = "toml_edit" 1354 | version = "0.20.2" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" 1357 | dependencies = [ 1358 | "indexmap", 1359 | "serde", 1360 | "serde_spanned", 1361 | "toml_datetime", 1362 | "winnow", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "unicode-ident" 1367 | version = "1.0.12" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1370 | 1371 | [[package]] 1372 | name = "utf8parse" 1373 | version = "0.2.1" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1376 | 1377 | [[package]] 1378 | name = "uuid" 1379 | version = "1.6.1" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" 1382 | dependencies = [ 1383 | "getrandom", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "version-compare" 1388 | version = "0.1.1" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 1391 | 1392 | [[package]] 1393 | name = "version_check" 1394 | version = "0.9.4" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1397 | 1398 | [[package]] 1399 | name = "wasi" 1400 | version = "0.11.0+wasi-snapshot-preview1" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1403 | 1404 | [[package]] 1405 | name = "wasm-bindgen" 1406 | version = "0.2.89" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" 1409 | dependencies = [ 1410 | "cfg-if", 1411 | "wasm-bindgen-macro", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "wasm-bindgen-backend" 1416 | version = "0.2.89" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" 1419 | dependencies = [ 1420 | "bumpalo", 1421 | "log", 1422 | "once_cell", 1423 | "proc-macro2", 1424 | "quote", 1425 | "syn 2.0.45", 1426 | "wasm-bindgen-shared", 1427 | ] 1428 | 1429 | [[package]] 1430 | name = "wasm-bindgen-macro" 1431 | version = "0.2.89" 1432 | source = "registry+https://github.com/rust-lang/crates.io-index" 1433 | checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" 1434 | dependencies = [ 1435 | "quote", 1436 | "wasm-bindgen-macro-support", 1437 | ] 1438 | 1439 | [[package]] 1440 | name = "wasm-bindgen-macro-support" 1441 | version = "0.2.89" 1442 | source = "registry+https://github.com/rust-lang/crates.io-index" 1443 | checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" 1444 | dependencies = [ 1445 | "proc-macro2", 1446 | "quote", 1447 | "syn 2.0.45", 1448 | "wasm-bindgen-backend", 1449 | "wasm-bindgen-shared", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "wasm-bindgen-shared" 1454 | version = "0.2.89" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" 1457 | 1458 | [[package]] 1459 | name = "winapi" 1460 | version = "0.3.9" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1463 | dependencies = [ 1464 | "winapi-i686-pc-windows-gnu", 1465 | "winapi-x86_64-pc-windows-gnu", 1466 | ] 1467 | 1468 | [[package]] 1469 | name = "winapi-i686-pc-windows-gnu" 1470 | version = "0.4.0" 1471 | source = "registry+https://github.com/rust-lang/crates.io-index" 1472 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1473 | 1474 | [[package]] 1475 | name = "winapi-util" 1476 | version = "0.1.6" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 1479 | dependencies = [ 1480 | "winapi", 1481 | ] 1482 | 1483 | [[package]] 1484 | name = "winapi-x86_64-pc-windows-gnu" 1485 | version = "0.4.0" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1488 | 1489 | [[package]] 1490 | name = "windows-core" 1491 | version = "0.52.0" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1494 | dependencies = [ 1495 | "windows-targets 0.52.0", 1496 | ] 1497 | 1498 | [[package]] 1499 | name = "windows-sys" 1500 | version = "0.48.0" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1503 | dependencies = [ 1504 | "windows-targets 0.48.5", 1505 | ] 1506 | 1507 | [[package]] 1508 | name = "windows-sys" 1509 | version = "0.52.0" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1512 | dependencies = [ 1513 | "windows-targets 0.52.0", 1514 | ] 1515 | 1516 | [[package]] 1517 | name = "windows-targets" 1518 | version = "0.48.5" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1521 | dependencies = [ 1522 | "windows_aarch64_gnullvm 0.48.5", 1523 | "windows_aarch64_msvc 0.48.5", 1524 | "windows_i686_gnu 0.48.5", 1525 | "windows_i686_msvc 0.48.5", 1526 | "windows_x86_64_gnu 0.48.5", 1527 | "windows_x86_64_gnullvm 0.48.5", 1528 | "windows_x86_64_msvc 0.48.5", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "windows-targets" 1533 | version = "0.52.0" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1536 | dependencies = [ 1537 | "windows_aarch64_gnullvm 0.52.0", 1538 | "windows_aarch64_msvc 0.52.0", 1539 | "windows_i686_gnu 0.52.0", 1540 | "windows_i686_msvc 0.52.0", 1541 | "windows_x86_64_gnu 0.52.0", 1542 | "windows_x86_64_gnullvm 0.52.0", 1543 | "windows_x86_64_msvc 0.52.0", 1544 | ] 1545 | 1546 | [[package]] 1547 | name = "windows_aarch64_gnullvm" 1548 | version = "0.48.5" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1551 | 1552 | [[package]] 1553 | name = "windows_aarch64_gnullvm" 1554 | version = "0.52.0" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1557 | 1558 | [[package]] 1559 | name = "windows_aarch64_msvc" 1560 | version = "0.48.5" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1563 | 1564 | [[package]] 1565 | name = "windows_aarch64_msvc" 1566 | version = "0.52.0" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1569 | 1570 | [[package]] 1571 | name = "windows_i686_gnu" 1572 | version = "0.48.5" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1575 | 1576 | [[package]] 1577 | name = "windows_i686_gnu" 1578 | version = "0.52.0" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1581 | 1582 | [[package]] 1583 | name = "windows_i686_msvc" 1584 | version = "0.48.5" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1587 | 1588 | [[package]] 1589 | name = "windows_i686_msvc" 1590 | version = "0.52.0" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1593 | 1594 | [[package]] 1595 | name = "windows_x86_64_gnu" 1596 | version = "0.48.5" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1599 | 1600 | [[package]] 1601 | name = "windows_x86_64_gnu" 1602 | version = "0.52.0" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1605 | 1606 | [[package]] 1607 | name = "windows_x86_64_gnullvm" 1608 | version = "0.48.5" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1611 | 1612 | [[package]] 1613 | name = "windows_x86_64_gnullvm" 1614 | version = "0.52.0" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1617 | 1618 | [[package]] 1619 | name = "windows_x86_64_msvc" 1620 | version = "0.48.5" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1623 | 1624 | [[package]] 1625 | name = "windows_x86_64_msvc" 1626 | version = "0.52.0" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1629 | 1630 | [[package]] 1631 | name = "winnow" 1632 | version = "0.5.31" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" 1635 | dependencies = [ 1636 | "memchr", 1637 | ] 1638 | --------------------------------------------------------------------------------