1use candid::{Decode, Encode, Nat};
2use garcon::Delay;
3use ic_agent::agent::http_transport::ReqwestHttpReplicaV2Transport;
4use ic_agent::{identity::Secp256k1Identity, Agent};
5use std::fs::{self};
6use std::path::Path;
7use uuid::Uuid;
8mod icsp_did;
9pub use icsp_did::{Buckets, FileBufExt, StoreArgs};
10
11const UPDATE_SIZE: usize = 2031616;
12
13pub async fn get_all_ic_file_key(
28 pem_identity_path: &str,
29 icsp_canister_id_text: &str,
30) -> Vec<String> {
31 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
32 let response_blob = build_agent(pem_identity_path)
33 .query(&canister_id, "getAllIcFileKey")
34 .with_arg(Encode!().expect("encode piece failed"))
35 .call()
36 .await
37 .expect("response error");
38 Decode!(&response_blob, Vec<String>).unwrap()
39}
40
41pub async fn get_file_info(
70 pem_identity_path: &str,
71 icsp_canister_id_text: &str,
72 file_key: String,
73) -> Option<FileBufExt> {
74 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
75 let response_blob = build_agent(pem_identity_path)
76 .query(&canister_id, "getFileInfo")
77 .with_arg(Encode!(&file_key).expect("encode piece failed"))
78 .call()
79 .await
80 .expect("response error");
81 Decode!(&response_blob, Option<FileBufExt>).unwrap()
82}
83
84pub async fn get_cycle_balance(pem_identity_path: &str, icsp_canister_id_text: &str) -> Nat {
101 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
102 let response_blob = build_agent(pem_identity_path)
103 .query(&canister_id, "getCycleBalance")
104 .with_arg(Encode!().expect("encode piece failed"))
105 .call()
106 .await
107 .expect("response error");
108 Decode!(&response_blob, Nat).unwrap()
109}
110
111pub async fn get_bucket_of_file(
133 pem_identity_path: &str,
134 icsp_canister_id_text: &str,
135 file_key: &str,
136) -> Option<candid::Principal> {
137 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
138 let response_blob = build_agent(pem_identity_path)
139 .query(&canister_id, "getBucketOfFile")
140 .with_arg(Encode!(&file_key).expect("encode piece failed"))
141 .call()
142 .await
143 .expect("response error");
144 Decode!(&response_blob, Option<candid::Principal>).unwrap()
145}
146
147pub async fn get_icsp_buckets(
181 pem_identity_path: &str,
182 icsp_canister_id_text: &str,
183) -> Option<Buckets> {
184 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
185 let response_blob = build_agent(pem_identity_path)
186 .query(&canister_id, "getBuckets")
187 .with_arg(Encode!().expect("encode piece failed"))
188 .call()
189 .await
190 .expect("response error");
191 Decode!(&response_blob, Option<Buckets>).unwrap()
192}
193
194pub async fn get_icsp_admins(
209 pem_identity_path: &str,
210 icsp_canister_id_text: &str,
211) -> Vec<candid::Principal> {
212 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
213 let response_blob = build_agent(pem_identity_path)
214 .query(&canister_id, "getAdmins")
215 .with_arg(Encode!().expect("encode error"))
216 .call()
217 .await
218 .expect("response error");
219 Decode!(&response_blob, Vec<candid::Principal>).unwrap()
220}
221
222pub async fn store_files(
249 pem_identity_path: &str,
250 folder_path: &str,
251 icsp_canister_id_text: &str,
252 is_http_open: bool,
253) -> Vec<(String, String)> {
254 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
255 let agent = build_agent(pem_identity_path);
256
257 let mut ans: Vec<(String, String)> = Vec::new();
258 let paths = fs::read_dir(&folder_path).unwrap();
259 for path in paths {
260 let file_path = path.unwrap().file_name().into_string().unwrap();
261 let pos: Vec<&str> = file_path.split(".").collect();
262 let file_name = String::from(pos[0]);
263 let file_type = String::from(pos[1]);
264 let file_extension = String::from(get_file_type(&file_type));
265 let s = folder_path.to_owned() + &file_path;
266
267 let (file_size, data_slice) = get_file_from_source(&s);
268 let file_key = Uuid::new_v4().to_string();
269 let puts = build_store_args(
270 file_key.clone(),
271 file_extension,
272 file_size.try_into().unwrap(),
273 &data_slice,
274 is_http_open,
275 );
276 for put in &puts {
277 let _response_blob = agent
278 .update(&canister_id, "store")
279 .with_arg(Encode!(put).expect("encode piece failed"))
280 .call_and_wait()
281 .await
282 .expect("response error");
283 }
284 ans.push((file_name.clone(), file_key.clone()));
285 }
286 ans
287}
288
289pub async fn store_file(
316 pem_identity_path: &str,
317 file_path_str: &str,
318 icsp_canister_id_text: &str,
319 is_http_open: bool,
320) -> (String, String) {
321 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
322 let agent = build_agent(pem_identity_path);
323 let file_path = Path::new(file_path_str);
324 let file_name = file_path.file_stem().unwrap().to_str().unwrap().to_owned();
325 let file_extension = String::from(get_file_type(
326 file_path.extension().unwrap().to_str().unwrap(),
327 ));
328
329 let (file_size, data_slice) = get_file_from_source(file_path_str);
330 let file_key = Uuid::new_v4().to_string();
331 let puts = build_store_args(
332 file_key.clone(),
333 file_extension,
334 file_size.try_into().unwrap(),
335 &data_slice,
336 is_http_open,
337 );
338 for put in &puts {
339 let _response_blob = agent
340 .update(&canister_id, "store")
341 .with_arg(Encode!(put).expect("encode piece failed"))
342 .call_and_wait()
343 .await
344 .expect("response error");
345 }
346 (file_name, file_key.clone())
347}
348
349pub async fn store_file_by_key(
373 pem_identity_path: &str,
374 file_path_str: &str,
375 icsp_canister_id_text: &str,
376 is_http_open: bool,
377 file_key: String,
378) -> (String, String) {
379 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
380 let agent = build_agent(pem_identity_path);
381 let file_path = Path::new(file_path_str);
382 let file_name = file_path.file_stem().unwrap().to_str().unwrap().to_owned();
383 let file_extension = String::from(get_file_type(
384 file_path.extension().unwrap().to_str().unwrap(),
385 ));
386
387 let (file_size, data_slice) = get_file_from_source(file_path_str);
388 let puts = build_store_args(
389 file_key.clone(),
390 file_extension,
391 file_size.try_into().unwrap(),
392 &data_slice,
393 is_http_open,
394 );
395 for put in &puts {
396 let _response_blob = agent
397 .update(&canister_id, "store")
398 .with_arg(Encode!(put).expect("encode piece failed"))
399 .call_and_wait()
400 .await
401 .expect("response error");
402 }
403 (file_name, file_key.clone())
404}
405
406pub async fn delete_file(pem_identity_path: &str, icsp_canister_id_text: &str, file_key: &str) {
424 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
425 let agent = build_agent(pem_identity_path);
426 let _ = agent
427 .update(&canister_id, "delete")
428 .with_arg(Encode!(&file_key.to_string()).expect("encode piece failed"))
429 .call_and_wait()
430 .await
431 .expect("response error");
432}
433
434pub async fn store_str(
461 pem_identity_path: &str,
462 icsp_canister_id_text: &str,
463 data: &str,
464 is_http_open: bool,
465) -> String {
466 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
467 let file_extension = "text/plain".to_string();
468 let file_size = data.len();
469 let file_key = Uuid::new_v4().to_string();
470
471 let put = StoreArgs {
472 key: file_key.clone(),
473 value: data.as_bytes().to_owned(),
474 total_index: Nat::from(1),
475 file_type: file_extension.clone(),
476 total_size: file_size.clone() as u64,
477 is_http_open: is_http_open.clone(),
478 index: Nat::from(0),
479 };
480 let _ = build_agent(pem_identity_path)
481 .update(&canister_id, "store")
482 .with_arg(Encode!(&put).expect("encode piece failed"))
483 .call_and_wait()
484 .await
485 .expect("response error");
486
487 file_key
488}
489
490pub async fn replace_str(
509 pem_identity_path: &str,
510 icsp_canister_id_text: &str,
511 file_key: &str,
512 data: &str,
513 is_http_open: bool,
514) {
515 delete_file(pem_identity_path, icsp_canister_id_text, file_key).await;
516
517 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
518 let put = StoreArgs {
519 key: file_key.to_string().to_owned(),
520 value: data.as_bytes().to_owned(),
521 total_index: Nat::from(1),
522 file_type: "text/plain".to_string().clone(),
523 total_size: data.len().clone() as u64,
524 is_http_open: is_http_open.clone(),
525 index: Nat::from(0),
526 };
527 let _ = build_agent(pem_identity_path)
528 .update(&canister_id, "store")
529 .with_arg(Encode!(&put).expect("encode piece failed"))
530 .call_and_wait()
531 .await
532 .expect("response error");
533}
534
535pub async fn get_file(
560 pem_identity_path: &str,
561 icsp_canister_id_text: &str,
562 file_key: &str,
563) -> (Vec<u8>, String) {
564 let bucket_canister_id = get_bucket_of_file(pem_identity_path, icsp_canister_id_text, file_key)
565 .await
566 .expect("can not find bucket have this file");
567 let agent = build_agent(pem_identity_path);
568
569 let total_index_blob = agent
570 .update(
571 &candid::Principal::from_text(bucket_canister_id.to_text()).unwrap(),
572 "getFileTotalIndex",
573 )
574 .with_arg(Encode!(&file_key).expect("encode failed"))
575 .call_and_wait()
576 .await
577 .expect("response error");
578 let total_index = Decode!(&total_index_blob, Nat).unwrap();
579 if total_index < Nat::from(1) {
580 return (vec![], "".to_string());
581 }
582 let mut index = 0;
583 let mut payload: Vec<u8> = Vec::new();
584 let mut file_type = "".to_string();
585 while Nat::from(index) < total_index {
586 let response_blob = agent
587 .query(
588 &candid::Principal::from_text(bucket_canister_id.to_text()).unwrap(),
589 "get",
590 )
591 .with_arg(Encode!(&file_key, &Nat::from(index)).expect("encode failed"))
592 .call()
593 .await
594 .expect("response error");
595 let mut response = Decode!(&response_blob, Option<(Vec<u8>, String)>)
596 .unwrap()
597 .expect("assets not have this file");
598 payload.append(&mut response.0);
599 index += 1;
600 if Nat::from(index) == total_index {
601 file_type = response.1;
602 }
603 }
604
605 (payload, file_type)
606}
607
608pub async fn add_icsp_admin(
625 pem_identity_path: &str,
626 icsp_canister_id_text: &str,
627 new_admin_text: &str,
628) {
629 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
630 let new_admin = candid::Principal::from_text(new_admin_text).unwrap();
631 let _ = build_agent(pem_identity_path)
632 .update(&canister_id, "addAdmin")
633 .with_arg(Encode!(&new_admin).expect("encode error"))
634 .call_and_wait()
635 .await
636 .expect("response error");
637}
638
639pub async fn delete_icsp_admin(
656 pem_identity_path: &str,
657 icsp_canister_id_text: &str,
658 old_admin_text: &str,
659) {
660 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
661 let old_admin = candid::Principal::from_text(old_admin_text).unwrap();
662 let _ = build_agent(pem_identity_path)
663 .update(&canister_id, "deleteAdmin")
664 .with_arg(Encode!(&old_admin).expect("encode error"))
665 .call_and_wait()
666 .await
667 .expect("response error");
668}
669
670pub async fn top_up_bucket(pem_identity_path: &str, icsp_canister_id_text: &str, amount: u64) {
689 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
690 let _ = build_agent(pem_identity_path)
691 .update(&canister_id, "topUpBucket")
692 .with_arg(Encode!(&Nat::from(amount)).expect("encode error"))
693 .call_and_wait()
694 .await
695 .expect("response error");
696}
697
698pub async fn get_version(pem_identity_path: &str, icsp_canister_id_text: &str) -> String {
713 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
714 let response_blob = build_agent(pem_identity_path)
715 .query(&canister_id, "getVersion")
716 .with_arg(Encode!().expect("encode error"))
717 .call()
718 .await
719 .expect("response error");
720 Decode!(&response_blob, String).unwrap()
721}
722
723pub async fn get_ic_file_numbers(
738 pem_identity_path: &str,
739 icsp_canister_id_text: &str,
740) -> Option<Nat> {
741 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
742 let response_blob = build_agent(pem_identity_path)
743 .query(&canister_id, "getIcFileNums")
744 .with_arg(Encode!().expect("encode error"))
745 .call()
746 .await
747 .expect("response error");
748 Decode!(&response_blob, Option<Nat>).unwrap()
749}
750
751pub async fn get_field_file_infos(
788 pem_identity_path: &str,
789 icsp_canister_id_text: &str,
790 page_number: u64,
791 page_index: u64,
792) -> Vec<FileBufExt> {
793 let canister_id = candid::Principal::from_text(icsp_canister_id_text).unwrap();
794 let response_blob = build_agent(pem_identity_path)
795 .query(&canister_id, "getFieldFileInfos")
796 .with_arg(Encode!(&Nat::from(page_number), &Nat::from(page_index)).expect("encode error"))
797 .call()
798 .await
799 .expect("response error");
800 Decode!(&response_blob, Vec<FileBufExt>).unwrap()
801}
802
803fn get_waiter() -> Delay {
804 let waiter = garcon::Delay::builder()
805 .throttle(std::time::Duration::from_millis(500))
806 .timeout(std::time::Duration::from_secs(60 * 5))
807 .build();
808 waiter
809}
810
811fn build_agent(pem_identity_path: &str) -> Agent {
812 let url = "https://ic0.app".to_string();
813 let identity = Secp256k1Identity::from_pem_file(String::from(pem_identity_path)).unwrap();
814 let transport = ReqwestHttpReplicaV2Transport::create(url).expect("transport error");
815 let agent = Agent::builder()
816 .with_transport(transport)
817 .with_identity(identity)
818 .build()
819 .expect("build agent error");
820 agent
821}
822
823fn get_file_from_source(path: &str) -> (usize, Vec<Vec<u8>>) {
825 let context = fs::read(path).expect("read file failed");
826 let size = context.len();
827 let slice_size = if context.len() % UPDATE_SIZE == 0 {
828 context.len() / UPDATE_SIZE
829 } else {
830 context.len() / UPDATE_SIZE + 1
831 };
832 let mut res = Vec::new();
833 for index in 0..slice_size {
834 if index == slice_size - 1 {
835 res.push(context[index * UPDATE_SIZE..context.len()].to_owned())
836 } else {
837 res.push(context[index * UPDATE_SIZE..(index + 1) * UPDATE_SIZE].to_owned())
838 }
839 }
840 (size, res)
841}
842
843fn build_store_args(
844 file_key: String,
845 file_extension: String,
846 total_size: u128,
847 data_slice: &Vec<Vec<u8>>,
848 is_open: bool,
849) -> Vec<StoreArgs> {
850 let mut order = 0;
851 let mut puts = vec![];
852 for data in data_slice {
853 puts.push(StoreArgs {
854 key: file_key.clone(),
855 value: data.to_owned(),
856 total_index: Nat::from(data_slice.len() as u128),
857 file_type: file_extension.clone(),
858 total_size: total_size.clone() as u64,
859 is_http_open: is_open,
860 index: Nat::from(order.clone()),
861 });
862 order += 1;
863 }
864 puts
865}
866
867fn get_file_type(file_type: &str) -> &str {
868 if file_type == "pdf" {
869 return "application/pdf";
870 } else if file_type == "jpg" || file_type == "jpeg" {
871 return "image/jpg";
872 } else if file_type == "png" {
873 return "image/png";
874 } else if file_type == "mp4" {
875 return "video/mp4";
876 } else if file_type == "mp3" {
877 return "audio/mp3";
878 } else if file_type == "gif" {
879 return "image/gif";
880 } else if file_type == "txt" {
881 return "text/plain";
882 } else if file_type == "ppt" || file_type == "pptx" {
883 return "application/vnd.ms-powerpoint";
884 } else if file_type == "html" || file_type == "xhtml" {
885 return "text/html";
886 } else if file_type == "doc" || file_type == "docx" {
887 return "application/msword";
888 } else if file_type == "xls" {
889 return "application/x-xls";
890 } else if file_type == "apk" {
891 return "application/vnd.android.package-archive";
892 } else if file_type == "svg" {
893 return "text/xml";
894 } else if file_type == "wmv" {
895 return "video/x-ms-wmv";
896 } else {
897 return "application/octet-stream";
898 }
899}