1mod codeview;
4mod error;
5
6use std::{
7 fs::File,
8 path::{Path, PathBuf},
9};
10
11pub use self::{codeview::CodeView, error::Error};
12
13pub const DEFAULT_SERVER_URL: &str = "http://msdl.microsoft.com/download/symbols";
14
15pub struct PdbDownloader {
16 codeview: CodeView,
17 servers: Vec<String>,
18 output: Option<PathBuf>,
19}
20
21impl PdbDownloader {
22 pub fn new(codeview: CodeView) -> Self {
23 Self {
24 codeview,
25 servers: vec![DEFAULT_SERVER_URL.into()],
26 output: None,
27 }
28 }
29
30 pub fn from_exe(path: impl AsRef<Path>) -> Result<Self, Error> {
31 Ok(Self::new(CodeView::from_path(path)?))
32 }
33
34 pub fn with_servers(self, servers: impl IntoIterator<Item = impl Into<String>>) -> Self {
35 Self {
36 servers: servers.into_iter().map(Into::into).collect(),
37 ..self
38 }
39 }
40
41 pub fn with_output(self, output: impl Into<PathBuf>) -> Self {
42 Self {
43 output: Some(output.into()),
44 ..self
45 }
46 }
47
48 pub fn download(self) -> Result<PathBuf, Error> {
49 let CodeView { path, guid } = self.codeview;
50
51 for server in &self.servers {
52 let path_with_underscore = path.chars().rev().skip(1).collect::<String>() + "_";
53
54 for suffix in &[&path, &path_with_underscore] {
55 let url = format!("{server}/{path}/{guid}/{suffix}");
56
57 tracing::info!(url, "requesting");
58 let response = reqwest::blocking::get(&url);
59 if response.is_err() {
60 continue;
61 }
62
63 let output = match &self.output {
64 Some(output) => {
65 if output.is_dir() {
66 output.join(format!("{guid}_{path}"))
67 }
68 else {
69 output.clone()
70 }
71 }
72 None => PathBuf::from(format!("{guid}_{path}")),
73 };
74
75 tracing::info!(?output, "downloading");
76 let mut file = File::create(&output)?;
77 response?.copy_to(&mut file)?;
78 return Ok(output);
79 }
80 }
81
82 Err(Error::Failed)
83 }
84}