Skip to content

Commit 8e6cb2b

Browse files
committed
add progress for server side
1 parent ffe36ba commit 8e6cb2b

File tree

7 files changed

+149
-59
lines changed

7 files changed

+149
-59
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ see some examples.
4242
- [ ] Isolate AsmResult as multiple different errors, and provide a better error message.
4343
- [ ] GUI interactions
4444
- [ ] GUI backend:
45+
- [x] add progress when loading files
4546
- [ ] unzip (whatever jar or dex) & parallel read
4647
- [ ] retrieve metadata and combine multiple metadata for better indexing
4748
- [x] using metadata to get the real data if needed (e.g. method instructions)
4849
- [ ] search content, quick search for metadata and slow search for instructions.
4950
- [ ] GUI frontend:
5051
- [x] basic window with egui.
5152
- [x] load files from the backend
53+
- [ ] add progress bar UI when loading files
5254
- [x] show metadata in a tree view
5355
- [x] show instructions in a list view
5456
- [ ] quick jump to specific metadata

asm_egui/src/smali.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ use java_asm::smali::{SmaliNode, SmaliToken};
66
#[derive(Default)]
77
struct SmaliHighlighter;
88

9+
// font, dft_color, dark_mode, smali_node
10+
impl ComputerMut<(&FontId, Color32, bool, &SmaliNode), LayoutJob> for SmaliHighlighter {
11+
fn compute(&mut self, key: (&FontId, Color32, bool, &SmaliNode)) -> LayoutJob {
12+
let (font, dft_color, dark_mode, smali_node) = key;
13+
let mut job = LayoutJob::default();
14+
let smali_style = if dark_mode { SmaliStyle::DARK } else { SmaliStyle::LIGHT };
15+
let max_offset_len = max_offset_hint(smali_node).to_string().len();
16+
append_node(&font, dft_color, &smali_style, smali_node, &mut job, 0, max_offset_len);
17+
job
18+
}
19+
}
20+
921
pub fn smali_layout(ui: &mut Ui, smali_node: &SmaliNode) {
1022
let ctx = &mut ui.ctx();
1123

12-
// font, dft_color, dark_mode, smali_node
13-
impl ComputerMut<(&FontId, Color32, bool, &SmaliNode), LayoutJob> for SmaliHighlighter {
14-
fn compute(&mut self, key: (&FontId, Color32, bool, &SmaliNode)) -> LayoutJob {
15-
let (font, dft_color, dark_mode, smali_node) = key;
16-
let mut job = LayoutJob::default();
17-
let smali_style = if dark_mode { SmaliStyle::DARK } else { SmaliStyle::LIGHT };
18-
let max_offset_len = max_offset_hint(smali_node).to_string().len();
19-
append_node(&font, dft_color, &smali_style, smali_node, &mut job, 0, max_offset_len);
20-
job
21-
}
22-
}
23-
2424
type HighlightCache = FrameCache<LayoutJob, SmaliHighlighter>;
2525

2626
let style = ui.ctx().style();

asm_server/src/impls/apk_load.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::impls::server::{ProgressMessage, ServerMessage};
12
use crate::server::OpenFileError;
23
use crate::Accessor;
34
use java_asm::dex::{ClassDef, DexFile, DexFileAccessor};
@@ -7,6 +8,7 @@ use log::{error, warn};
78
use std::collections::HashMap;
89
use std::io::{Read, Seek};
910
use std::sync::Arc;
11+
use tokio::sync::mpsc::Sender;
1012
use zip::ZipArchive;
1113

1214
pub struct ApkAccessor {
@@ -15,7 +17,9 @@ pub struct ApkAccessor {
1517

1618
type ClassPosition = (Arc<DexFileAccessor>, ClassDef);
1719

18-
pub fn read_apk(zip_archive: ZipArchive<impl Read + Seek>) -> Result<ApkAccessor, OpenFileError> {
20+
pub async fn read_apk(
21+
zip_archive: ZipArchive<impl Read + Seek>, sender: Sender<ServerMessage>,
22+
) -> Result<ApkAccessor, OpenFileError> {
1923
let mut zip_archive = zip_archive;
2024
// read dex files
2125
let mut dex_files = zip_archive
@@ -32,6 +36,7 @@ pub fn read_apk(zip_archive: ZipArchive<impl Read + Seek>) -> Result<ApkAccessor
3236
}).filter_map(|v|v).collect();
3337

3438
// put dex files
39+
let dex_file_count = dex_files.len();
3540
let dex_files = dex_files.iter().map(|name| {
3641
let mut file = zip_archive.by_index(*name).map_err(OpenFileError::LoadZip)?;
3742
let mut bytes = Vec::new();
@@ -46,10 +51,9 @@ pub fn read_apk(zip_archive: ZipArchive<impl Read + Seek>) -> Result<ApkAccessor
4651
None
4752
}
4853
}
49-
}).filter_map(|v| v).collect::<Vec<_>>();
50-
let classes_count = dex_files.iter().map(|dex_file| dex_file.file.class_defs.len()).sum();
51-
let mut map = HashMap::with_capacity(classes_count);
52-
for dex_file in dex_files {
54+
}).filter_map(|v| v);
55+
let mut map = HashMap::new();
56+
for (index, dex_file) in dex_files.enumerate() {
5357
for class_def in dex_file.file.class_defs.iter() {
5458
let class_idx = class_def.class_idx;
5559
let class_name = dex_file.get_type(class_idx);
@@ -63,11 +67,34 @@ pub fn read_apk(zip_archive: ZipArchive<impl Read + Seek>) -> Result<ApkAccessor
6367
error!("Error when reading class name {}: {:?}", class_idx, class_name);
6468
}
6569
}
70+
send_progress(&sender, index + 1, dex_file_count).await;
6671
};
6772
map.shrink_to_fit();
73+
send_loaded(&sender).await;
6874
Ok(ApkAccessor { map })
6975
}
7076

77+
async fn send_progress(
78+
sender: &Sender<ServerMessage>, current: usize, total: usize,
79+
) {
80+
let progress = current as f32 / total as f32;
81+
let message = ServerMessage::Progress(ProgressMessage {
82+
progress,
83+
in_loading: true,
84+
});
85+
sender.send(message).await.unwrap();
86+
}
87+
88+
async fn send_loaded(
89+
sender: &Sender<ServerMessage>,
90+
) {
91+
let message = ServerMessage::Progress(ProgressMessage {
92+
progress: 1.0,
93+
in_loading: false,
94+
});
95+
sender.send(message).await.unwrap();
96+
}
97+
7198
// classes.dex -> 0
7299
// classes2.dex -> 2
73100
#[inline]

asm_server/src/impls/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11

22
pub(crate) mod apk_load;
3+
pub(crate) mod server;
4+
pub(crate) mod util;

asm_server/src/impls/server.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use crate::ui::AppContainer;
2+
use crate::AsmServer;
3+
use log::info;
4+
use std::time::Instant;
5+
6+
pub enum ServerMessage {
7+
Progress(ProgressMessage),
8+
}
9+
10+
pub struct ProgressMessage {
11+
// 0.0 - 1.0
12+
pub progress: f32,
13+
pub in_loading: bool,
14+
}
15+
16+
pub struct FileOpenContext {
17+
pub path: String,
18+
pub start_time: Instant,
19+
}
20+
21+
22+
impl AsmServer {
23+
pub fn on_file_opened(
24+
&self,
25+
context: &FileOpenContext,
26+
render_target: AppContainer,
27+
) {
28+
let FileOpenContext { path, start_time } = context;
29+
info!("open file {path} cost: {:?}", start_time.elapsed());
30+
self.render_to_app(render_target);
31+
}
32+
}

asm_server/src/impls/util.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::future::Future;
2+
use std::sync::Arc;
3+
use tokio::runtime;
4+
use tokio::runtime::Runtime;
5+
6+
pub fn new_tokio_thread<F, Fut>(async_logic: F)
7+
where
8+
F: FnOnce(Arc<Runtime>) -> Fut + Send + 'static,
9+
Fut: Future,
10+
{
11+
std::thread::spawn(move || {
12+
let runtime: Arc<Runtime> = runtime::Builder::new_multi_thread()
13+
.enable_all().build().unwrap().into();
14+
let copied_runtime = runtime.clone();
15+
runtime.block_on(async move {
16+
let runtime = copied_runtime;
17+
async_logic(runtime).await;
18+
});
19+
});
20+
}

asm_server/src/server.rs

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use crate::impls::apk_load::read_apk;
2+
use crate::impls::server::{FileOpenContext, ServerMessage};
3+
use crate::impls::util::new_tokio_thread;
24
use crate::ui::{AppContainer, DirInfo, Left};
35
use crate::{Accessor, AccessorEnum, AccessorMut, AsmServer, LoadingState, ServerMut};
46
use java_asm::smali::SmaliNode;
@@ -7,9 +9,10 @@ use log::{error, info};
79
use std::fs::File;
810
use std::io::{Read, Seek};
911
use std::ops::{Deref, DerefMut};
10-
use std::sync::{Arc, Mutex};
12+
use std::sync::Arc;
1113
use std::time::Instant;
12-
use tokio::runtime;
14+
use tokio::sync::mpsc;
15+
use tokio::sync::mpsc::Sender;
1316
use zip::result::ZipError;
1417
use zip::ZipArchive;
1518

@@ -28,53 +31,61 @@ impl AsmServer {
2831

2932
pub fn smart_open(server: ServerMut, path: &str, render_target: AppContainer) {
3033
let context = FileOpenContext { path: path.to_string(), start_time: Instant::now() };
31-
std::thread::spawn(move || {
32-
let tokio_runtime = runtime::Builder::new_multi_thread()
33-
.enable_all().build().unwrap();
34-
tokio_runtime.block_on(async move {
35-
let new_server = AsmServer::new();
36-
let path = &context.path;
37-
let accessor = new_server.accessor.clone();
38-
if path.ends_with(".apk") {
39-
let opened_file = File::open(path);
40-
match opened_file {
41-
Ok(opened_file) => {
42-
let read_result = Self::from_apk(opened_file, accessor);
43-
if let Err(e) = read_result {
44-
error!("resolve file meets an error. {e:?}");
45-
}
34+
new_tokio_thread(|runtime| async move {
35+
let (sender, receiver) = mpsc::channel::<ServerMessage>(5);
36+
37+
let new_server = AsmServer::new();
38+
*server.lock() = Some(new_server.clone());
39+
40+
let server_for_receiver = server.clone();
41+
runtime.spawn(async move {
42+
let server = server_for_receiver;
43+
let mut receiver = receiver;
44+
while let Some(msg) = receiver.recv().await {
45+
let mut server = server.lock();
46+
let server_ref = server.deref_mut();
47+
let Some(server_ref) = server_ref else { continue };
48+
match msg {
49+
ServerMessage::Progress(progress) => {
50+
println!("progress: {}", progress.progress);
51+
server_ref.loading_state.loading_progress = progress.progress;
52+
server_ref.loading_state.in_loading = progress.in_loading;
4653
}
47-
Err(e) => {
48-
let error = OpenFileError::Io(e);
49-
error!("read {path} meet an io error. {error:?}");
54+
}
55+
}
56+
});
57+
58+
let path = &context.path;
59+
let accessor = new_server.accessor.clone();
60+
if path.ends_with(".apk") {
61+
let opened_file = File::open(path);
62+
match opened_file {
63+
Ok(opened_file) => {
64+
let read_result = Self::from_apk(opened_file, sender, accessor).await;
65+
if let Err(e) = read_result {
66+
error!("resolve file meets an error. {e:?}");
5067
}
5168
}
52-
} else {
53-
error!("unsupported file type: {:?}", path);
54-
};
55-
*server.lock() = Some(new_server.clone());
56-
new_server.on_file_opened(&context, render_target);
57-
})
69+
Err(e) => {
70+
let error = OpenFileError::Io(e);
71+
error!("read {path} meet an io error. {error:?}");
72+
}
73+
}
74+
} else {
75+
error!("unsupported file type: {:?}", path);
76+
};
77+
new_server.on_file_opened(&context, render_target);
5878
});
5979
}
6080

61-
fn on_file_opened(
62-
&self,
63-
context: &FileOpenContext,
64-
render_target: AppContainer,
65-
) {
66-
let FileOpenContext { path, start_time } = context;
67-
info!("open file {path} cost: {:?}", start_time.elapsed());
68-
self.render_to_app(render_target);
69-
}
70-
71-
pub fn from_apk(
81+
pub async fn from_apk(
7282
apk_content: impl Read + Seek,
83+
sender: Sender<ServerMessage>,
7384
accessor: AccessorMut,
7485
) -> Result<(), OpenFileError> {
7586
let zip = ZipArchive::new(apk_content)
7687
.map_err(OpenFileError::LoadZip)?;
77-
let apk_accessor = read_apk(zip)?;
88+
let apk_accessor = read_apk(zip, sender).await?;
7889
// safe unwrap, no other places in current thread will access it.
7990
*accessor.lock() = Some(AccessorEnum::Apk(apk_accessor));
8091
Ok(())
@@ -85,10 +96,6 @@ impl AsmServer {
8596
}
8697
}
8798

88-
struct FileOpenContext {
89-
pub path: String,
90-
pub start_time: Instant,
91-
}
9299

93100
/// input operation processing
94101
impl AsmServer {

0 commit comments

Comments
 (0)