@tutao/node-mimimi 259.250106.0 → 259.250108.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/package.json +5 -5
- package/src/importer.rs +146 -153
- package/src/importer_api.rs +136 -69
- package/src/logging/console.rs +90 -66
package/Cargo.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tutao/node-mimimi",
|
|
3
|
-
"version": "259.
|
|
3
|
+
"version": "259.250108.1",
|
|
4
4
|
"main": "./dist/binding.cjs",
|
|
5
5
|
"types": "./dist/binding.d.ts",
|
|
6
6
|
"napi": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@tutao/otest": "259.
|
|
19
|
+
"@tutao/otest": "259.250108.1",
|
|
20
20
|
"@napi-rs/cli": "^2.18.4",
|
|
21
21
|
"typescript": "5.3.3",
|
|
22
22
|
"zx": "8.1.5"
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"version": "napi version"
|
|
34
34
|
},
|
|
35
35
|
"optionalDependencies": {
|
|
36
|
-
"@tutao/node-mimimi-win32-x64-msvc": "259.
|
|
37
|
-
"@tutao/node-mimimi-linux-x64-gnu": "259.
|
|
38
|
-
"@tutao/node-mimimi-darwin-universal": "259.
|
|
36
|
+
"@tutao/node-mimimi-win32-x64-msvc": "259.250108.1",
|
|
37
|
+
"@tutao/node-mimimi-linux-x64-gnu": "259.250108.1",
|
|
38
|
+
"@tutao/node-mimimi-darwin-universal": "259.250108.1"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/importer.rs
CHANGED
|
@@ -8,10 +8,10 @@ use file_reader::{FileImport, FileIterationError};
|
|
|
8
8
|
use imap_reader::ImapImportConfig;
|
|
9
9
|
use imap_reader::{ImapImport, ImapIterationError};
|
|
10
10
|
use importable_mail::ImportableMail;
|
|
11
|
+
use napi::tokio::sync::{Mutex, MutexGuard};
|
|
11
12
|
use std::fs;
|
|
12
|
-
use std::future::Future;
|
|
13
13
|
use std::path::PathBuf;
|
|
14
|
-
use std::sync::Arc;
|
|
14
|
+
use std::sync::{Arc, OnceLock};
|
|
15
15
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
16
16
|
use tutasdk::blobs::blob_facade::FileData;
|
|
17
17
|
use tutasdk::crypto::aes;
|
|
@@ -20,6 +20,7 @@ use tutasdk::crypto::key::{GenericAesKey, VersionedAesKey};
|
|
|
20
20
|
use tutasdk::crypto::randomizer_facade::RandomizerFacade;
|
|
21
21
|
use tutasdk::entities::generated::sys::{BlobReferenceTokenWrapper, StringWrapper};
|
|
22
22
|
|
|
23
|
+
use std::collections::HashMap;
|
|
23
24
|
use tutasdk::entities::generated::tutanota::{
|
|
24
25
|
ImportAttachment, ImportMailGetIn, ImportMailPostIn, ImportMailPostOut, ImportMailState,
|
|
25
26
|
};
|
|
@@ -41,6 +42,12 @@ pub const MAX_REQUEST_SIZE: usize = 1024 * 1024 * 8;
|
|
|
41
42
|
#[cfg(test)]
|
|
42
43
|
pub const MAX_REQUEST_SIZE: usize = 1024 * 5;
|
|
43
44
|
|
|
45
|
+
type MailboxId = String;
|
|
46
|
+
|
|
47
|
+
pub static GLOBAL_IMPORTER_STATES: OnceLock<
|
|
48
|
+
Mutex<HashMap<MailboxId, Arc<Mutex<LocalImportState>>>>,
|
|
49
|
+
> = OnceLock::new();
|
|
50
|
+
|
|
44
51
|
// We need this type because IdTupleGenerated cannot be converted to a napi value.
|
|
45
52
|
#[cfg_attr(feature = "javascript", napi_derive::napi(object))]
|
|
46
53
|
#[cfg_attr(test, derive(Debug))]
|
|
@@ -115,6 +122,8 @@ pub enum ImportError {
|
|
|
115
122
|
FileDeletionError(std::io::Error, PathBuf),
|
|
116
123
|
IOError(std::io::Error),
|
|
117
124
|
CannotLoadMailbox,
|
|
125
|
+
ImporterAlreadyRunning,
|
|
126
|
+
NoRunningImport,
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
#[derive(Debug)]
|
|
@@ -148,6 +157,7 @@ pub enum ImportStatus {
|
|
|
148
157
|
Paused = 1,
|
|
149
158
|
Canceled = 2,
|
|
150
159
|
Finished = 3,
|
|
160
|
+
Error = 4,
|
|
151
161
|
}
|
|
152
162
|
|
|
153
163
|
/// A running import can be stopped or paused
|
|
@@ -179,6 +189,7 @@ pub struct LocalImportState {
|
|
|
179
189
|
pub total_count: i64,
|
|
180
190
|
pub success_count: i64,
|
|
181
191
|
pub failed_count: i64,
|
|
192
|
+
pub import_progress_action: ImportProgressAction,
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
pub struct ImportEssential {
|
|
@@ -189,13 +200,6 @@ pub struct ImportEssential {
|
|
|
189
200
|
randomizer_facade: RandomizerFacade,
|
|
190
201
|
}
|
|
191
202
|
|
|
192
|
-
pub struct Importer {
|
|
193
|
-
pub state: LocalImportState,
|
|
194
|
-
pub essentials: ImportEssential,
|
|
195
|
-
source: ImportSource,
|
|
196
|
-
import_directory: PathBuf,
|
|
197
|
-
}
|
|
198
|
-
|
|
199
203
|
pub enum ImportSource {
|
|
200
204
|
RemoteImap { imap_import_client: Box<ImapImport> },
|
|
201
205
|
LocalFile { fs_email_client: FileImport },
|
|
@@ -233,13 +237,6 @@ impl Iterator for ImportSource {
|
|
|
233
237
|
|
|
234
238
|
pub type ImportableMailsButcher<Source> =
|
|
235
239
|
super::reduce_to_chunks::Butcher<{ MAX_REQUEST_SIZE }, AttachmentUploadData, Source>;
|
|
236
|
-
impl Importer {
|
|
237
|
-
fn make_random_aggregate_id(randomizer_facade: &RandomizerFacade) -> CustomId {
|
|
238
|
-
let new_id_bytes = randomizer_facade.generate_random_array::<4>();
|
|
239
|
-
let new_id_string = BASE64_URL_SAFE_NO_PAD.encode(new_id_bytes);
|
|
240
|
-
CustomId(new_id_string)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
240
|
|
|
244
241
|
impl ImportEssential {
|
|
245
242
|
const IMPORT_DISABLED_ERR: ApiCallError = ApiCallError::ServerResponseError {
|
|
@@ -472,12 +469,24 @@ impl ImportEssential {
|
|
|
472
469
|
}
|
|
473
470
|
|
|
474
471
|
impl LocalImportState {
|
|
475
|
-
fn change_status(&mut self, new_status: ImportStatus) {
|
|
472
|
+
pub(crate) fn change_status(&mut self, new_status: ImportStatus) {
|
|
476
473
|
self.current_status = new_status;
|
|
477
474
|
}
|
|
478
475
|
}
|
|
479
476
|
|
|
477
|
+
pub struct Importer {
|
|
478
|
+
pub(super) state: Arc<Mutex<LocalImportState>>,
|
|
479
|
+
pub(super) essentials: ImportEssential,
|
|
480
|
+
source: ImportSource,
|
|
481
|
+
import_directory: PathBuf,
|
|
482
|
+
}
|
|
480
483
|
impl Importer {
|
|
484
|
+
fn make_random_aggregate_id(randomizer_facade: &RandomizerFacade) -> CustomId {
|
|
485
|
+
let new_id_bytes = randomizer_facade.generate_random_array::<4>();
|
|
486
|
+
let new_id_string = BASE64_URL_SAFE_NO_PAD.encode(new_id_bytes);
|
|
487
|
+
CustomId(new_id_string)
|
|
488
|
+
}
|
|
489
|
+
|
|
481
490
|
pub async fn create_imap_importer(
|
|
482
491
|
logged_in_sdk: Arc<LoggedInSdk>,
|
|
483
492
|
target_owner_group: GeneratedId,
|
|
@@ -543,6 +552,7 @@ impl Importer {
|
|
|
543
552
|
let randomizer_facade = RandomizerFacade::from_core(rand::rngs::OsRng);
|
|
544
553
|
|
|
545
554
|
Self {
|
|
555
|
+
state: Arc::new(Mutex::new(LocalImportState::new())),
|
|
546
556
|
source: import_source,
|
|
547
557
|
essentials: ImportEssential {
|
|
548
558
|
logged_in_sdk,
|
|
@@ -551,23 +561,24 @@ impl Importer {
|
|
|
551
561
|
target_mailset,
|
|
552
562
|
randomizer_facade,
|
|
553
563
|
},
|
|
554
|
-
state: LocalImportState::new(),
|
|
555
564
|
import_directory,
|
|
556
565
|
}
|
|
557
566
|
}
|
|
558
567
|
|
|
559
|
-
fn update_remote_state_id(
|
|
560
|
-
|
|
568
|
+
async fn update_remote_state_id(
|
|
569
|
+
&self,
|
|
561
570
|
state_id: IdTupleGenerated,
|
|
562
571
|
import_directory: PathBuf,
|
|
563
572
|
) -> Result<(), ImportError> {
|
|
564
573
|
let min_id = GeneratedId::min_id();
|
|
565
574
|
|
|
566
|
-
|
|
567
|
-
|
|
575
|
+
let remote_state_id = self.get_state(|state| state.remote_state_id.clone()).await;
|
|
576
|
+
if remote_state_id.list_id == min_id.as_str()
|
|
577
|
+
&& remote_state_id.element_id == min_id.as_str()
|
|
568
578
|
{
|
|
569
579
|
let generated = state_id.clone();
|
|
570
|
-
|
|
580
|
+
self.update_state(|mut state| state.remote_state_id = state_id.clone().into())
|
|
581
|
+
.await;
|
|
571
582
|
let mut state_id_file = import_directory.clone();
|
|
572
583
|
state_id_file.push("import_mail_state");
|
|
573
584
|
|
|
@@ -575,8 +586,9 @@ impl Importer {
|
|
|
575
586
|
return Ok(());
|
|
576
587
|
}
|
|
577
588
|
|
|
589
|
+
let remote_state_id = self.get_state(|state| state.remote_state_id.clone()).await;
|
|
578
590
|
// once id is set, it should always be same
|
|
579
|
-
if
|
|
591
|
+
if remote_state_id != state_id.into() {
|
|
580
592
|
return Err(ImportError::InconsistentStateId);
|
|
581
593
|
}
|
|
582
594
|
|
|
@@ -594,31 +606,28 @@ impl Importer {
|
|
|
594
606
|
.load::<ImportMailState, _>(&id)
|
|
595
607
|
.await
|
|
596
608
|
}
|
|
597
|
-
async fn mark_remote_final_state(
|
|
598
|
-
&
|
|
599
|
-
|
|
609
|
+
pub(super) async fn mark_remote_final_state(
|
|
610
|
+
logged_in_sdk: &LoggedInSdk,
|
|
611
|
+
local_state: &LocalImportState,
|
|
600
612
|
) -> Result<(), ImportError> {
|
|
601
613
|
assert!(
|
|
602
|
-
|
|
603
|
-
||
|
|
604
|
-
||
|
|
614
|
+
local_state.current_status == ImportStatus::Finished
|
|
615
|
+
|| local_state.current_status == ImportStatus::Canceled
|
|
616
|
+
|| local_state.current_status == ImportStatus::Paused,
|
|
605
617
|
"only cancel and finished should be final state"
|
|
606
618
|
);
|
|
607
619
|
|
|
608
620
|
// we reached final state before making first call, was either empty mails or was cancelled before making first post call
|
|
609
|
-
if
|
|
621
|
+
if local_state.remote_state_id
|
|
610
622
|
!= IdTupleGenerated::new(GeneratedId::min_id(), GeneratedId::min_id()).into()
|
|
611
623
|
{
|
|
612
|
-
let mut import_state =
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
.
|
|
617
|
-
.map_err(|e| ImportError::sdk("loading importState before Finished", e))?;
|
|
618
|
-
import_state.status = final_status as i64;
|
|
624
|
+
let mut import_state =
|
|
625
|
+
Self::load_import_state(logged_in_sdk, local_state.remote_state_id.clone())
|
|
626
|
+
.await
|
|
627
|
+
.map_err(|e| ImportError::sdk("loading importState before Finished", e))?;
|
|
628
|
+
import_state.status = local_state.current_status as i64;
|
|
619
629
|
|
|
620
|
-
|
|
621
|
-
.logged_in_sdk
|
|
630
|
+
logged_in_sdk
|
|
622
631
|
.mail_facade()
|
|
623
632
|
.get_crypto_entity_client()
|
|
624
633
|
.update_instance(import_state)
|
|
@@ -630,11 +639,10 @@ impl Importer {
|
|
|
630
639
|
}
|
|
631
640
|
|
|
632
641
|
pub async fn import_next_chunk(&mut self) -> Result<(), ImportError> {
|
|
642
|
+
let import_essentials = &self.essentials;
|
|
633
643
|
let Self {
|
|
634
|
-
essentials: import_essentials,
|
|
635
644
|
source: import_source,
|
|
636
|
-
|
|
637
|
-
import_directory,
|
|
645
|
+
..
|
|
638
646
|
} = self;
|
|
639
647
|
|
|
640
648
|
let attachment_upload_data = import_source.into_iter().map(|importable_mail| {
|
|
@@ -648,17 +656,17 @@ impl Importer {
|
|
|
648
656
|
ImportableMailsButcher::new(attachment_upload_data, |upload_data| {
|
|
649
657
|
estimate_json_size(&upload_data.keyed_import_mail_data.import_mail_data)
|
|
650
658
|
});
|
|
651
|
-
|
|
652
659
|
match chunked_mails_provider.next() {
|
|
653
660
|
// everything have been finished
|
|
654
661
|
None => {
|
|
655
|
-
self.state.change_status(ImportStatus::Finished)
|
|
662
|
+
self.update_state(|mut state| state.change_status(ImportStatus::Finished))
|
|
663
|
+
.await;
|
|
656
664
|
Ok(())
|
|
657
665
|
},
|
|
658
666
|
|
|
659
667
|
// this chunk was too big to import
|
|
660
668
|
Some(Err(_too_big_chunk)) => {
|
|
661
|
-
self.state.failed_count += 1;
|
|
669
|
+
self.update_state(|mut state| state.failed_count += 1).await;
|
|
662
670
|
Err(ImportError::TooBigChunk)?
|
|
663
671
|
},
|
|
664
672
|
|
|
@@ -674,35 +682,32 @@ impl Importer {
|
|
|
674
682
|
.map(|id| id.keyed_import_mail_data.eml_file_path.clone())
|
|
675
683
|
.collect();
|
|
676
684
|
|
|
685
|
+
let mut failed_count: i64 = 0;
|
|
686
|
+
let remote_state_id = self.get_state(|state| state.remote_state_id.clone()).await;
|
|
677
687
|
let unit_import_data = import_essentials
|
|
678
688
|
.upload_attachments_for_chunk(chunked_import_data)
|
|
679
689
|
.await
|
|
680
|
-
.inspect_err(|_e|
|
|
681
|
-
import_state.failed_count += import_count_in_this_chunk;
|
|
682
|
-
})?;
|
|
690
|
+
.inspect_err(|_e| failed_count += import_count_in_this_chunk)?;
|
|
683
691
|
let importable_post_data = import_essentials
|
|
684
|
-
.make_serialized_chunk(
|
|
685
|
-
import_state.remote_state_id.clone().into(),
|
|
686
|
-
unit_import_data,
|
|
687
|
-
)
|
|
692
|
+
.make_serialized_chunk(remote_state_id.into(), unit_import_data)
|
|
688
693
|
.await
|
|
689
|
-
.inspect_err(|_e|
|
|
690
|
-
import_state.failed_count += import_count_in_this_chunk;
|
|
691
|
-
})?;
|
|
694
|
+
.inspect_err(|_e| failed_count += import_count_in_this_chunk)?;
|
|
692
695
|
|
|
693
696
|
let import_mails_post_out = import_essentials
|
|
694
697
|
.make_import_service_call(importable_post_data)
|
|
695
698
|
.await
|
|
696
|
-
.inspect_err(|_e|
|
|
697
|
-
import_state.failed_count += import_count_in_this_chunk;
|
|
698
|
-
})?;
|
|
699
|
+
.inspect_err(|_e| failed_count += import_count_in_this_chunk)?;
|
|
699
700
|
|
|
700
|
-
|
|
701
|
-
|
|
701
|
+
self.update_state(|mut state| {
|
|
702
|
+
state.failed_count += failed_count;
|
|
703
|
+
state.success_count += import_count_in_this_chunk;
|
|
704
|
+
})
|
|
705
|
+
.await;
|
|
706
|
+
self.update_remote_state_id(
|
|
702
707
|
import_mails_post_out.mailState,
|
|
703
|
-
import_directory.clone(),
|
|
704
|
-
)
|
|
705
|
-
|
|
708
|
+
self.import_directory.clone(),
|
|
709
|
+
)
|
|
710
|
+
.await?;
|
|
706
711
|
for eml_file_path in eml_file_paths.into_iter().flatten() {
|
|
707
712
|
fs::remove_file(&eml_file_path)
|
|
708
713
|
.map_err(|e| ImportError::FileDeletionError(e, eml_file_path))?;
|
|
@@ -713,49 +718,45 @@ impl Importer {
|
|
|
713
718
|
}
|
|
714
719
|
}
|
|
715
720
|
|
|
716
|
-
pub async fn start_stateful_import<
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
self.mark_remote_final_state(ImportStatus::Canceled).await?;
|
|
737
|
-
Self::delete_import_dir(&self.import_directory)?;
|
|
738
|
-
callback_handle(self.state.clone()).await?;
|
|
739
|
-
break 'import Ok(());
|
|
740
|
-
},
|
|
741
|
-
ImportProgressAction::Continue => {
|
|
742
|
-
self.import_next_chunk().await?;
|
|
743
|
-
if self.state.current_status == ImportStatus::Finished {
|
|
744
|
-
self.mark_remote_final_state(ImportStatus::Finished).await?;
|
|
745
|
-
Self::delete_import_dir(&self.import_directory)?;
|
|
746
|
-
break 'import Ok(());
|
|
747
|
-
}
|
|
748
|
-
},
|
|
721
|
+
pub async fn start_stateful_import(&mut self) -> Result<(), ImportError> {
|
|
722
|
+
self.update_state(|mut state| state.change_status(ImportStatus::Running))
|
|
723
|
+
.await;
|
|
724
|
+
let mut import_progress_action = ImportProgressAction::Continue;
|
|
725
|
+
|
|
726
|
+
while import_progress_action == ImportProgressAction::Continue {
|
|
727
|
+
self.import_next_chunk().await?;
|
|
728
|
+
|
|
729
|
+
let updated_state = self.get_state(|state| state.clone()).await;
|
|
730
|
+
import_progress_action = updated_state.import_progress_action;
|
|
731
|
+
eprintln!(
|
|
732
|
+
"Import state Id: {}",
|
|
733
|
+
updated_state.remote_state_id.element_id
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
if updated_state.current_status == ImportStatus::Finished {
|
|
737
|
+
Importer::mark_remote_final_state(&self.essentials.logged_in_sdk, &updated_state)
|
|
738
|
+
.await?;
|
|
739
|
+
Self::delete_import_dir(&self.import_directory)?;
|
|
740
|
+
break;
|
|
749
741
|
}
|
|
750
742
|
}
|
|
743
|
+
|
|
744
|
+
Ok(())
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
pub(super) async fn update_state<F: Fn(MutexGuard<LocalImportState>)>(&self, f: F) {
|
|
748
|
+
f(self.state.lock().await)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
pub(super) async fn get_state<T>(&self, f: fn(state: MutexGuard<LocalImportState>) -> T) -> T {
|
|
752
|
+
f(self.state.lock().await)
|
|
751
753
|
}
|
|
752
754
|
|
|
753
755
|
pub(super) async fn get_resumable_import(
|
|
754
756
|
config_directory: String,
|
|
755
757
|
mailbox_id: String,
|
|
756
758
|
) -> Result<ResumableImport, ImportError> {
|
|
757
|
-
let import_directory_path =
|
|
758
|
-
Importer::get_import_directory(config_directory, GeneratedId::from(mailbox_id));
|
|
759
|
+
let import_directory_path = Importer::get_import_directory(config_directory, &mailbox_id);
|
|
759
760
|
let mut state_file_path = import_directory_path.clone();
|
|
760
761
|
state_file_path.push("import_mail_state");
|
|
761
762
|
|
|
@@ -777,19 +778,19 @@ impl Importer {
|
|
|
777
778
|
}
|
|
778
779
|
}
|
|
779
780
|
|
|
780
|
-
Self::delete_import_dir(&
|
|
781
|
+
Self::delete_import_dir(&import_directory_path)?;
|
|
781
782
|
|
|
782
783
|
Err(ImportError::NoElementIdForState)
|
|
783
784
|
}
|
|
784
785
|
|
|
785
|
-
fn delete_import_dir(import_directory_path: &PathBuf) -> Result<(), ImportError> {
|
|
786
|
+
pub(super) fn delete_import_dir(import_directory_path: &PathBuf) -> Result<(), ImportError> {
|
|
786
787
|
if import_directory_path.exists() {
|
|
787
788
|
fs::remove_dir_all(import_directory_path)
|
|
788
789
|
.map_err(|e| ImportError::FileDeletionError(e, import_directory_path.clone()))?;
|
|
789
790
|
}
|
|
790
791
|
Ok(())
|
|
791
792
|
}
|
|
792
|
-
pub fn get_import_directory(config_directory: String, mailbox_id:
|
|
793
|
+
pub fn get_import_directory(config_directory: String, mailbox_id: &str) -> PathBuf {
|
|
793
794
|
[
|
|
794
795
|
config_directory,
|
|
795
796
|
"current_imports".into(),
|
|
@@ -827,6 +828,7 @@ impl LocalImportState {
|
|
|
827
828
|
total_count: 0,
|
|
828
829
|
success_count: 0,
|
|
829
830
|
failed_count: 0,
|
|
831
|
+
import_progress_action: ImportProgressAction::Continue,
|
|
830
832
|
}
|
|
831
833
|
}
|
|
832
834
|
}
|
|
@@ -856,18 +858,12 @@ mod tests {
|
|
|
856
858
|
new_count
|
|
857
859
|
}
|
|
858
860
|
pub async fn import_all_of_source(importer: &mut Importer) -> Result<(), ImportError> {
|
|
859
|
-
importer
|
|
860
|
-
.start_stateful_import(|_| async {
|
|
861
|
-
Ok(StateCallbackResponse {
|
|
862
|
-
action: ImportProgressAction::Continue,
|
|
863
|
-
})
|
|
864
|
-
})
|
|
865
|
-
.await
|
|
861
|
+
importer.start_stateful_import().await
|
|
866
862
|
}
|
|
867
863
|
|
|
868
864
|
fn assert_same_remote_and_local_state(
|
|
869
865
|
remote_state: &ImportMailState,
|
|
870
|
-
local_state:
|
|
866
|
+
local_state: MutexGuard<LocalImportState>,
|
|
871
867
|
) {
|
|
872
868
|
// todo! sug
|
|
873
869
|
// assert_eq!(remote_state.status, local_state.current_status as i64);
|
|
@@ -996,13 +992,14 @@ mod tests {
|
|
|
996
992
|
greenmail.store_mail("sug@example.org", email_second.as_str());
|
|
997
993
|
|
|
998
994
|
import_all_of_source(&mut importer).await.unwrap();
|
|
999
|
-
let
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
995
|
+
let remote_state_id = importer
|
|
996
|
+
.get_state(|state| state.remote_state_id.clone())
|
|
997
|
+
.await;
|
|
998
|
+
let remote_state =
|
|
999
|
+
Importer::load_import_state(&importer.essentials.logged_in_sdk, remote_state_id)
|
|
1000
|
+
.await
|
|
1001
|
+
.unwrap();
|
|
1002
|
+
assert_same_remote_and_local_state(&remote_state, importer.state.lock().await);
|
|
1006
1003
|
|
|
1007
1004
|
assert_eq!(remote_state.status, ImportStatus::Finished as i64);
|
|
1008
1005
|
assert_eq!(remote_state.failedMails, 0);
|
|
@@ -1017,14 +1014,15 @@ mod tests {
|
|
|
1017
1014
|
greenmail.store_mail("sug@example.org", email.as_str());
|
|
1018
1015
|
|
|
1019
1016
|
import_all_of_source(&mut importer).await.unwrap();
|
|
1020
|
-
let
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1017
|
+
let remote_state_id = importer
|
|
1018
|
+
.get_state(|state| state.remote_state_id.clone())
|
|
1019
|
+
.await;
|
|
1020
|
+
let remote_state =
|
|
1021
|
+
Importer::load_import_state(&importer.essentials.logged_in_sdk, remote_state_id)
|
|
1022
|
+
.await
|
|
1023
|
+
.unwrap();
|
|
1026
1024
|
|
|
1027
|
-
assert_same_remote_and_local_state(&remote_state,
|
|
1025
|
+
assert_same_remote_and_local_state(&remote_state, importer.state.lock().await);
|
|
1028
1026
|
assert_eq!(remote_state.status, ImportStatus::Finished as i64);
|
|
1029
1027
|
assert_eq!(remote_state.failedMails, 0);
|
|
1030
1028
|
assert_eq!(remote_state.successfulMails, 1);
|
|
@@ -1034,14 +1032,15 @@ mod tests {
|
|
|
1034
1032
|
async fn can_import_single_eml_file_without_attachment() {
|
|
1035
1033
|
let mut importer = init_file_importer(vec!["sample.eml"]).await;
|
|
1036
1034
|
import_all_of_source(&mut importer).await.unwrap();
|
|
1037
|
-
let
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1035
|
+
let remote_state_id = importer
|
|
1036
|
+
.get_state(|state| state.remote_state_id.clone())
|
|
1037
|
+
.await;
|
|
1038
|
+
let remote_state =
|
|
1039
|
+
Importer::load_import_state(&importer.essentials.logged_in_sdk, remote_state_id)
|
|
1040
|
+
.await
|
|
1041
|
+
.unwrap();
|
|
1043
1042
|
|
|
1044
|
-
assert_same_remote_and_local_state(&remote_state,
|
|
1043
|
+
assert_same_remote_and_local_state(&remote_state, importer.state.lock().await);
|
|
1045
1044
|
assert_eq!(remote_state.status, ImportStatus::Finished as i64);
|
|
1046
1045
|
assert_eq!(remote_state.failedMails, 0);
|
|
1047
1046
|
assert_eq!(remote_state.successfulMails, 1);
|
|
@@ -1051,14 +1050,15 @@ mod tests {
|
|
|
1051
1050
|
async fn can_import_single_eml_file_with_attachment() {
|
|
1052
1051
|
let mut importer = init_file_importer(vec!["attachment_sample.eml"]).await;
|
|
1053
1052
|
import_all_of_source(&mut importer).await.unwrap();
|
|
1054
|
-
let
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1053
|
+
let remote_state_id = importer
|
|
1054
|
+
.get_state(|state| state.remote_state_id.clone())
|
|
1055
|
+
.await;
|
|
1056
|
+
let remote_state =
|
|
1057
|
+
Importer::load_import_state(&importer.essentials.logged_in_sdk, remote_state_id)
|
|
1058
|
+
.await
|
|
1059
|
+
.unwrap();
|
|
1060
1060
|
|
|
1061
|
-
assert_same_remote_and_local_state(&remote_state,
|
|
1061
|
+
assert_same_remote_and_local_state(&remote_state, importer.state.lock().await);
|
|
1062
1062
|
assert_eq!(remote_state.status, ImportStatus::Finished as i64);
|
|
1063
1063
|
assert_eq!(remote_state.failedMails, 0);
|
|
1064
1064
|
assert_eq!(remote_state.successfulMails, 1);
|
|
@@ -1069,21 +1069,14 @@ mod tests {
|
|
|
1069
1069
|
async fn should_stop_if_on_stop_action() {
|
|
1070
1070
|
let mut importer = init_file_importer(vec!["sample.eml"; 3]).await;
|
|
1071
1071
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
.unwrap();
|
|
1081
|
-
let remote_state = Importer::load_import_state(
|
|
1082
|
-
&importer.essentials.logged_in_sdk,
|
|
1083
|
-
importer.state.remote_state_id.clone(),
|
|
1084
|
-
)
|
|
1085
|
-
.await
|
|
1086
|
-
.unwrap();
|
|
1072
|
+
importer.start_stateful_import().await.unwrap();
|
|
1073
|
+
let remote_state_id = importer
|
|
1074
|
+
.get_state(|state| state.remote_state_id.clone())
|
|
1075
|
+
.await;
|
|
1076
|
+
let remote_state =
|
|
1077
|
+
Importer::load_import_state(&importer.essentials.logged_in_sdk, remote_state_id)
|
|
1078
|
+
.await
|
|
1079
|
+
.unwrap();
|
|
1087
1080
|
|
|
1088
1081
|
assert_eq!(remote_state.status, ImportStatus::Canceled as i64);
|
|
1089
1082
|
assert_eq!(remote_state.failedMails, 0);
|
package/src/importer_api.rs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
use super::importer::{
|
|
2
|
-
ImportError, ImportMailStateId,
|
|
3
|
-
|
|
2
|
+
ImportError, ImportMailStateId, ImportProgressAction, ImportStatus, Importer, IterationError,
|
|
3
|
+
LocalImportState, ResumableImport, GLOBAL_IMPORTER_STATES,
|
|
4
4
|
};
|
|
5
5
|
use crate::importer::file_reader::FileImport;
|
|
6
|
-
use napi::
|
|
7
|
-
use napi::
|
|
6
|
+
use napi::tokio::sync::Mutex;
|
|
7
|
+
use napi::tokio::sync::MutexGuard;
|
|
8
8
|
use napi::Env;
|
|
9
|
+
use std::collections::HashMap;
|
|
9
10
|
use std::fs;
|
|
10
11
|
use std::path::PathBuf;
|
|
11
12
|
use std::sync::Arc;
|
|
@@ -13,17 +14,6 @@ use tutasdk::login::{CredentialType, Credentials};
|
|
|
13
14
|
use tutasdk::net::native_rest_client::NativeRestClient;
|
|
14
15
|
use tutasdk::{GeneratedId, IdTupleGenerated, LoggedInSdk};
|
|
15
16
|
|
|
16
|
-
pub type NapiTokioMutex<T> = napi::tokio::sync::Mutex<T>;
|
|
17
|
-
|
|
18
|
-
/// Javascript function to check for state change
|
|
19
|
-
type StateCallback =
|
|
20
|
-
ThreadsafeFunction<LocalImportState, napi::threadsafe_function::ErrorStrategy::Fatal>;
|
|
21
|
-
|
|
22
|
-
#[napi_derive::napi]
|
|
23
|
-
pub struct ImporterApi {
|
|
24
|
-
pub(crate) inner: Arc<NapiTokioMutex<Importer>>,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
#[napi_derive::napi(object)]
|
|
28
18
|
#[derive(Clone)]
|
|
29
19
|
/// Passed in from js-side, will be validated before being converted to proper tuta sdk credentials.
|
|
@@ -38,18 +28,29 @@ pub struct TutaCredentials {
|
|
|
38
28
|
pub is_internal_credential: bool,
|
|
39
29
|
}
|
|
40
30
|
|
|
31
|
+
#[napi_derive::napi]
|
|
32
|
+
pub struct ImporterApi {}
|
|
33
|
+
|
|
41
34
|
impl ImporterApi {
|
|
35
|
+
pub async fn get_running_imports<'a>(
|
|
36
|
+
) -> MutexGuard<'a, HashMap<String, Arc<Mutex<LocalImportState>>>> {
|
|
37
|
+
GLOBAL_IMPORTER_STATES
|
|
38
|
+
.get_or_init(|| Mutex::new(HashMap::new()))
|
|
39
|
+
.lock()
|
|
40
|
+
.await
|
|
41
|
+
}
|
|
42
|
+
|
|
42
43
|
pub async fn create_file_importer_inner(
|
|
43
44
|
logged_in_sdk: Arc<LoggedInSdk>,
|
|
44
45
|
target_owner_group: String,
|
|
45
46
|
target_mailset: IdTupleGenerated,
|
|
46
47
|
source_paths: Vec<PathBuf>,
|
|
47
48
|
import_directory: PathBuf,
|
|
48
|
-
) -> napi::Result<
|
|
49
|
+
) -> napi::Result<Importer> {
|
|
49
50
|
let target_owner_group = GeneratedId(target_owner_group);
|
|
50
51
|
|
|
51
52
|
let source_count = source_paths.len() as i64;
|
|
52
|
-
let
|
|
53
|
+
let importer = Importer::create_file_importer(
|
|
53
54
|
logged_in_sdk,
|
|
54
55
|
target_owner_group,
|
|
55
56
|
target_mailset,
|
|
@@ -58,10 +59,11 @@ impl ImporterApi {
|
|
|
58
59
|
)
|
|
59
60
|
.await?;
|
|
60
61
|
|
|
61
|
-
importer
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
importer
|
|
63
|
+
.update_state(|mut state| state.total_count = source_count)
|
|
64
|
+
.await;
|
|
65
|
+
|
|
66
|
+
Ok(importer)
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
async fn create_sdk(
|
|
@@ -83,64 +85,77 @@ impl ImporterApi {
|
|
|
83
85
|
#[napi_derive::napi]
|
|
84
86
|
impl ImporterApi {
|
|
85
87
|
#[napi]
|
|
86
|
-
pub async
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
Ok(promise) => promise.await,
|
|
96
|
-
Err(e) => Err(e),
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
let mut importer = self.inner.lock().await;
|
|
101
|
-
importer
|
|
102
|
-
.start_stateful_import(callback_handle_provider)
|
|
103
|
-
.await?;
|
|
104
|
-
|
|
105
|
-
Ok(())
|
|
88
|
+
pub async fn get_import_state(mailbox_id: String) -> napi::Result<Option<LocalImportState>> {
|
|
89
|
+
let locked_importer_states = Self::get_running_imports().await;
|
|
90
|
+
match locked_importer_states.get(&mailbox_id) {
|
|
91
|
+
Some(locked_state) => Ok(Some({
|
|
92
|
+
let state = locked_state.lock().await.clone();
|
|
93
|
+
state
|
|
94
|
+
})),
|
|
95
|
+
None => Ok(None),
|
|
96
|
+
}
|
|
106
97
|
}
|
|
107
98
|
|
|
108
99
|
#[napi]
|
|
109
|
-
pub async fn
|
|
100
|
+
pub async fn start_file_import(
|
|
101
|
+
mailbox_id: String,
|
|
110
102
|
tuta_credentials: TutaCredentials,
|
|
111
103
|
target_owner_group: String,
|
|
112
104
|
target_mailset_id: (String, String),
|
|
113
105
|
source_paths: Vec<String>,
|
|
114
106
|
config_directory: String,
|
|
115
|
-
) -> napi::Result<
|
|
116
|
-
let (target_mailset_lid, target_mailset_eid) = target_mailset_id;
|
|
117
|
-
|
|
107
|
+
) -> napi::Result<()> {
|
|
118
108
|
let logged_in_sdk = ImporterApi::create_sdk(tuta_credentials).await?;
|
|
109
|
+
|
|
110
|
+
let mut running_imports = Self::get_running_imports().await;
|
|
111
|
+
if let Some(import) = running_imports.get_mut(&mailbox_id) {
|
|
112
|
+
let current_status = import.lock().await.current_status;
|
|
113
|
+
if current_status != ImportStatus::Running {
|
|
114
|
+
running_imports.remove(&mailbox_id);
|
|
115
|
+
} else {
|
|
116
|
+
Err(ImportError::ImporterAlreadyRunning)?;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let (target_mailset_lid, target_mailset_eid) = target_mailset_id;
|
|
119
121
|
let target_mailset = IdTupleGenerated::new(
|
|
120
122
|
GeneratedId(target_mailset_lid),
|
|
121
123
|
GeneratedId(target_mailset_eid),
|
|
122
124
|
);
|
|
123
|
-
let mailbox_id = logged_in_sdk
|
|
124
|
-
.mail_facade()
|
|
125
|
-
.load_user_mailbox()
|
|
126
|
-
.await
|
|
127
|
-
.map_err(|e| ImportError::sdk("loading mailbox", e))?
|
|
128
|
-
._id
|
|
129
|
-
.ok_or(ImportError::CannotLoadMailbox)?;
|
|
130
125
|
let import_directory: PathBuf =
|
|
131
|
-
Importer::get_import_directory(config_directory, mailbox_id);
|
|
126
|
+
Importer::get_import_directory(config_directory, &mailbox_id);
|
|
132
127
|
let source_paths = source_paths.into_iter().map(PathBuf::from).collect();
|
|
133
128
|
let eml_sources = FileImport::prepare_import(import_directory.clone(), source_paths)
|
|
134
129
|
.map_err(|e| ImportError::IterationError(IterationError::File(e)))?;
|
|
135
130
|
|
|
136
|
-
Self::create_file_importer_inner(
|
|
131
|
+
let inner = Self::create_file_importer_inner(
|
|
137
132
|
logged_in_sdk,
|
|
138
133
|
target_owner_group,
|
|
139
134
|
target_mailset,
|
|
140
135
|
eml_sources,
|
|
141
136
|
import_directory,
|
|
142
137
|
)
|
|
143
|
-
.await
|
|
138
|
+
.await?;
|
|
139
|
+
|
|
140
|
+
running_imports.insert(mailbox_id.clone(), inner.state.clone());
|
|
141
|
+
drop(running_imports);
|
|
142
|
+
|
|
143
|
+
Self::spawn_importer_task(inner);
|
|
144
|
+
Ok(())
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fn spawn_importer_task(mut inner: Importer) {
|
|
148
|
+
napi::tokio::task::spawn(async move {
|
|
149
|
+
match inner.start_stateful_import().await {
|
|
150
|
+
Ok(_) => {},
|
|
151
|
+
Err(e) => {
|
|
152
|
+
log::error!("Importer task failed: {:?}", e);
|
|
153
|
+
inner
|
|
154
|
+
.update_state(|mut state| state.change_status(ImportStatus::Error))
|
|
155
|
+
.await;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
});
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
#[napi]
|
|
@@ -155,10 +170,11 @@ impl ImporterApi {
|
|
|
155
170
|
|
|
156
171
|
#[napi]
|
|
157
172
|
pub async fn resume_file_import(
|
|
173
|
+
mailbox_id: String,
|
|
158
174
|
tuta_credentials: TutaCredentials,
|
|
159
175
|
mail_state_id: ImportMailStateId,
|
|
160
176
|
config_directory: String,
|
|
161
|
-
) -> napi::Result<
|
|
177
|
+
) -> napi::Result<()> {
|
|
162
178
|
let logged_in_sdk = ImporterApi::create_sdk(tuta_credentials).await?;
|
|
163
179
|
let import_state = Importer::load_import_state(&logged_in_sdk, mail_state_id)
|
|
164
180
|
.await
|
|
@@ -168,15 +184,8 @@ impl ImporterApi {
|
|
|
168
184
|
let target_owner_group = import_state
|
|
169
185
|
._ownerGroup
|
|
170
186
|
.expect("import state should have ownerGroup");
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
.load_user_mailbox()
|
|
174
|
-
.await
|
|
175
|
-
.map_err(|e| ImportError::sdk("loading mailbox", e))?
|
|
176
|
-
._id
|
|
177
|
-
.ok_or(ImportError::CannotLoadMailbox)?;
|
|
178
|
-
let import_directory: PathBuf =
|
|
179
|
-
Importer::get_import_directory(config_directory, mailbox_id);
|
|
187
|
+
|
|
188
|
+
let import_directory = Importer::get_import_directory(config_directory, &mailbox_id);
|
|
180
189
|
|
|
181
190
|
let dir_entries = fs::read_dir(&import_directory)?;
|
|
182
191
|
let mut source_paths: Vec<PathBuf> = vec![];
|
|
@@ -191,24 +200,82 @@ impl ImporterApi {
|
|
|
191
200
|
}
|
|
192
201
|
}
|
|
193
202
|
|
|
194
|
-
Self::create_file_importer_inner(
|
|
203
|
+
let inner = Self::create_file_importer_inner(
|
|
195
204
|
logged_in_sdk,
|
|
196
205
|
target_owner_group.as_str().to_string(),
|
|
197
206
|
target_mailset,
|
|
198
207
|
source_paths,
|
|
199
208
|
import_directory,
|
|
200
209
|
)
|
|
201
|
-
.await
|
|
210
|
+
.await?;
|
|
211
|
+
|
|
212
|
+
{
|
|
213
|
+
let mut running_imports = Self::get_running_imports().await;
|
|
214
|
+
running_imports.insert(mailbox_id.clone(), inner.state.clone());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
Self::spawn_importer_task(inner);
|
|
218
|
+
Ok(())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[napi]
|
|
222
|
+
pub async fn set_progress_action(
|
|
223
|
+
mailbox_id: String,
|
|
224
|
+
tuta_credentials: TutaCredentials,
|
|
225
|
+
import_progress_action: ImportProgressAction,
|
|
226
|
+
config_directory: String,
|
|
227
|
+
) -> napi::Result<()> {
|
|
228
|
+
let mut running_imports = Self::get_running_imports().await;
|
|
229
|
+
let locked_local_state = running_imports.get_mut(mailbox_id.as_str());
|
|
230
|
+
let import_directory_path = Importer::get_import_directory(config_directory, &mailbox_id);
|
|
231
|
+
|
|
232
|
+
match locked_local_state {
|
|
233
|
+
Some(local_import_state) => {
|
|
234
|
+
let logged_in_sdk = ImporterApi::create_sdk(tuta_credentials).await?;
|
|
235
|
+
let mut local_import_state = local_import_state.lock().await;
|
|
236
|
+
local_import_state.import_progress_action = import_progress_action;
|
|
237
|
+
|
|
238
|
+
match import_progress_action {
|
|
239
|
+
ImportProgressAction::Continue => Ok(()),
|
|
240
|
+
|
|
241
|
+
ImportProgressAction::Pause => {
|
|
242
|
+
local_import_state.current_status = ImportStatus::Paused;
|
|
243
|
+
Importer::mark_remote_final_state(&logged_in_sdk, &local_import_state)
|
|
244
|
+
.await?;
|
|
245
|
+
|
|
246
|
+
Ok(())
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
ImportProgressAction::Stop => {
|
|
250
|
+
let previous_status = local_import_state.current_status;
|
|
251
|
+
local_import_state.current_status = ImportStatus::Canceled;
|
|
252
|
+
Importer::mark_remote_final_state(&logged_in_sdk, &local_import_state)
|
|
253
|
+
.await?;
|
|
254
|
+
|
|
255
|
+
if previous_status != ImportStatus::Running {
|
|
256
|
+
Importer::delete_import_dir(&import_directory_path)?;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
Ok(())
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
None => {
|
|
265
|
+
Importer::delete_import_dir(&import_directory_path)?;
|
|
266
|
+
Ok(())
|
|
267
|
+
},
|
|
268
|
+
}
|
|
202
269
|
}
|
|
203
270
|
|
|
204
271
|
#[napi]
|
|
205
|
-
pub
|
|
272
|
+
pub fn init_log(env: Env) {
|
|
206
273
|
// this is in a separate fn because Env isn't Send, so can't be used in async fn.
|
|
207
274
|
crate::logging::console::Console::init(env)
|
|
208
275
|
}
|
|
209
276
|
|
|
210
277
|
#[napi]
|
|
211
|
-
pub
|
|
278
|
+
pub fn deinit_log() {
|
|
212
279
|
crate::logging::console::Console::deinit();
|
|
213
280
|
}
|
|
214
281
|
}
|
package/src/logging/console.rs
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
use crate::logging::logger::{LogLevel, LogMessage, Logger};
|
|
2
2
|
use log::{Level, LevelFilter, Log, Metadata, Record};
|
|
3
|
-
use napi::
|
|
4
|
-
use
|
|
5
|
-
use std::sync::
|
|
3
|
+
use napi::Env;
|
|
4
|
+
use std::sync::mpsc::Sender;
|
|
5
|
+
use std::sync::Once;
|
|
6
|
+
use std::sync::RwLock;
|
|
6
7
|
|
|
7
8
|
const TAG: &str = file!();
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
/// Maintain one instance of the logger, as the log crate can only have the logger be set exactly
|
|
11
|
+
/// once.
|
|
12
|
+
static GLOBAL_CONSOLE: Console = Console {
|
|
13
|
+
sender: RwLock::new(None),
|
|
14
|
+
};
|
|
10
15
|
|
|
11
16
|
/// A way for the rust code to log messages to the main applications log files
|
|
12
17
|
/// without having to deal with obtaining a reference to console each time.
|
|
13
|
-
#[derive(Clone)]
|
|
14
18
|
pub struct Console {
|
|
15
|
-
|
|
19
|
+
sender: RwLock<Option<Sender<LogMessage>>>,
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
impl Log for Console {
|
|
@@ -28,56 +32,59 @@ impl Log for Console {
|
|
|
28
32
|
|
|
29
33
|
let tag = record.file().unwrap_or("<unknown>").to_string();
|
|
30
34
|
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
let lock = self.sender.read().expect("poisoned");
|
|
36
|
+
if let Some(sender) = lock.as_ref() {
|
|
37
|
+
let _ = sender.send(LogMessage {
|
|
38
|
+
level: record.metadata().level().into(),
|
|
39
|
+
message: format!("{}", record.args()),
|
|
40
|
+
tag,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
fn flush(&self) {}
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
impl Console {
|
|
42
|
-
pub
|
|
43
|
-
|
|
49
|
+
pub fn init(env: Env) {
|
|
50
|
+
Self::init_global_state();
|
|
51
|
+
|
|
52
|
+
let mut current_sender = GLOBAL_CONSOLE.sender.write().expect("poisoned");
|
|
53
|
+
if current_sender.is_some() {
|
|
44
54
|
// some other thread already initialized the cell, we don't need to set up the logger.
|
|
45
55
|
return;
|
|
46
56
|
}
|
|
57
|
+
|
|
47
58
|
let (tx, rx) = std::sync::mpsc::channel::<LogMessage>();
|
|
48
|
-
let console = Console { tx };
|
|
49
59
|
let logger = Logger::new(rx);
|
|
50
60
|
let maybe_async_task = env.spawn(logger);
|
|
51
61
|
|
|
52
62
|
match maybe_async_task {
|
|
53
63
|
Ok(_logger_task) => {
|
|
54
|
-
|
|
64
|
+
*current_sender = Some(tx);
|
|
65
|
+
drop(current_sender);
|
|
66
|
+
GLOBAL_CONSOLE.log(
|
|
55
67
|
&Record::builder()
|
|
56
68
|
.level(Level::Info)
|
|
57
69
|
.file(Some(TAG))
|
|
58
70
|
.args(format_args!("{}", "spawned logger"))
|
|
59
71
|
.build(),
|
|
60
72
|
);
|
|
61
|
-
|
|
62
|
-
GLOBAL_CONSOLE
|
|
63
|
-
.set(console)
|
|
64
|
-
.map_err(|_| "can not set")
|
|
65
|
-
.unwrap();
|
|
66
|
-
|
|
67
|
-
let console = GLOBAL_CONSOLE.get().unwrap();
|
|
68
|
-
set_panic_hook(&console);
|
|
69
|
-
log::set_logger(console).unwrap_or_else(|e| eprintln!("failed to set logger: {e}"));
|
|
70
|
-
log::set_max_level(LevelFilter::Info);
|
|
71
73
|
},
|
|
72
74
|
Err(e) => {
|
|
73
75
|
eprintln!("failed to spawn logger: {e}");
|
|
74
76
|
},
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.
|
|
79
|
+
|
|
80
|
+
pub fn deinit() {
|
|
81
|
+
let sender = GLOBAL_CONSOLE
|
|
82
|
+
.sender
|
|
83
|
+
.write()
|
|
84
|
+
.expect("poisoned")
|
|
85
|
+
.take()
|
|
86
|
+
.expect("cannot deinit logger before initializing");
|
|
87
|
+
sender
|
|
81
88
|
.send(LogMessage {
|
|
82
89
|
level: LogLevel::Finish,
|
|
83
90
|
message: "called deinit".to_string(),
|
|
@@ -85,42 +92,59 @@ impl Console {
|
|
|
85
92
|
})
|
|
86
93
|
.expect("Can not send finish log message. Receiver already disconnected");
|
|
87
94
|
}
|
|
88
|
-
}
|
|
89
95
|
|
|
90
|
-
///
|
|
91
|
-
///
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
96
|
+
/// Sets the panic hook and global logger.
|
|
97
|
+
///
|
|
98
|
+
/// This function must be called once before the console can be used, but it can be safely
|
|
99
|
+
/// called multiple times, and it is thread-safe.
|
|
100
|
+
fn init_global_state() {
|
|
101
|
+
// Limit scope to this function only.
|
|
102
|
+
static GLOBAL_CONSOLE_SETUP: Once = Once::new();
|
|
103
|
+
|
|
104
|
+
GLOBAL_CONSOLE_SETUP.call_once(|| {
|
|
105
|
+
// Sets the logger to the static instance and sets up the panic hook.
|
|
106
|
+
// This can fail if the logger was set somewhere else.
|
|
107
|
+
if let Err(e) = log::set_logger(&GLOBAL_CONSOLE) {
|
|
108
|
+
eprintln!("failed to set logger: {e}");
|
|
109
|
+
} else {
|
|
110
|
+
log::set_max_level(LevelFilter::Info);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// set a panic hook that tries to log the panic to the JS side before continuing
|
|
114
|
+
// a normal unwind. should work unless the panicking thread is the main thread.
|
|
115
|
+
let logger_thread_id = std::thread::current().id();
|
|
116
|
+
let old_panic_hook = std::panic::take_hook();
|
|
117
|
+
|
|
118
|
+
std::panic::set_hook(Box::new(move |panic_info| {
|
|
119
|
+
let formatted_info = panic_info.to_string();
|
|
120
|
+
let formatted_stack = std::backtrace::Backtrace::force_capture().to_string();
|
|
121
|
+
if logger_thread_id == std::thread::current().id() {
|
|
122
|
+
// logger is (probably) running on the currently panicking thread,
|
|
123
|
+
// so we can't use it to log to JS. this at least shows up in stderr.
|
|
124
|
+
eprintln!("PANIC MAIN {}", formatted_info);
|
|
125
|
+
eprintln!("PANIC MAIN {}", formatted_stack);
|
|
126
|
+
} else {
|
|
127
|
+
GLOBAL_CONSOLE.log(
|
|
128
|
+
&Record::builder()
|
|
129
|
+
.level(Level::Error)
|
|
130
|
+
.file(Some("PANIC"))
|
|
131
|
+
.args(format_args!(
|
|
132
|
+
"thread {} {}",
|
|
133
|
+
std::thread::current().name().unwrap_or("<unknown>"),
|
|
134
|
+
formatted_info
|
|
135
|
+
))
|
|
136
|
+
.build(),
|
|
137
|
+
);
|
|
138
|
+
GLOBAL_CONSOLE.log(
|
|
139
|
+
&Record::builder()
|
|
140
|
+
.level(Level::Error)
|
|
141
|
+
.file(Some("PANIC"))
|
|
142
|
+
.args(format_args!("{}", formatted_stack.as_str()))
|
|
143
|
+
.build(),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
old_panic_hook(panic_info)
|
|
147
|
+
}));
|
|
148
|
+
});
|
|
149
|
+
}
|
|
126
150
|
}
|