Initial commit
This commit is contained in:
parent
24c8eee0e7
commit
5fdfb01b13
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
1379
Cargo.lock
generated
Normal file
1379
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "odd"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-process = "1.2.0"
|
||||
async-recursion = "0.3.2"
|
||||
futures = "0.3.17"
|
||||
reqwest = { version = "0.11.4", features = ["rustls-tls", "serde_json", "json"] }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde_json = "1.0.68"
|
||||
tokio = { version = "1.10.1", features = ["full"] }
|
235
src/main.rs
Normal file
235
src/main.rs
Normal file
|
@ -0,0 +1,235 @@
|
|||
use crate::types::{Children, DriveItem};
|
||||
use async_recursion::async_recursion;
|
||||
use futures::StreamExt;
|
||||
use std::{collections::HashMap, path::PathBuf, process::Command};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
mod types;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Node {
|
||||
Folder(FolderNode),
|
||||
File(FileNode),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct FolderNode {
|
||||
name: String,
|
||||
childrens: HashMap<String, Node>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct FileNode {
|
||||
name: String,
|
||||
size: u64,
|
||||
content_download_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DownloadEntry {
|
||||
file: FileNode,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let share_ids = [
|
||||
// Add the IDs heree
|
||||
];
|
||||
|
||||
for share_id in share_ids {
|
||||
let tree = match generate_directory_structure(share_id).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("error in getting share details: {}", e),
|
||||
};
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
path.push(std::env::current_dir().unwrap());
|
||||
path.push("downloads");
|
||||
path.push(share_id.replace('!', "-").replace("/", "-"));
|
||||
|
||||
let mut store = vec![];
|
||||
download_tree(&tree, &mut path, &mut store);
|
||||
|
||||
println!("{:?}", store);
|
||||
|
||||
let fetches = futures::stream::iter(store).for_each_concurrent(4, |entry| async move {
|
||||
//
|
||||
if let Err(e) = download(entry.file, entry.path).await {
|
||||
println!("ERRORORORORORORORORO in writing file: {}", e);
|
||||
};
|
||||
});
|
||||
fetches.await;
|
||||
|
||||
// println!("{:?}", tree);
|
||||
}
|
||||
}
|
||||
|
||||
fn download_tree(tree: &HashMap<String, Node>, path: &mut PathBuf, store: &mut Vec<DownloadEntry>) {
|
||||
for (_, val) in tree {
|
||||
match val {
|
||||
Node::File(f) => {
|
||||
println!("filename = {} path = {:?}", f.name, path.as_path());
|
||||
|
||||
Command::new("/usr/bin/mkdir")
|
||||
.arg("-p")
|
||||
.arg(path.clone())
|
||||
.spawn()
|
||||
.expect("error in creating directories");
|
||||
|
||||
store.push(DownloadEntry {
|
||||
file: f.clone(),
|
||||
path: path.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Node::Folder(f) => {
|
||||
path.push(f.name.clone());
|
||||
download_tree(&f.childrens, path, store);
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn download(file: FileNode, mut path: PathBuf) -> Result<(), reqwest::Error> {
|
||||
path.push(file.name.clone());
|
||||
|
||||
if let Ok(f) = File::open(&path).await {
|
||||
if let Ok(meta) = f.metadata().await {
|
||||
if meta.len() == file.size {
|
||||
println!(
|
||||
"SKIPPING FILE name: {} got size: {} path: {:?}",
|
||||
file.name,
|
||||
meta.len(),
|
||||
path
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
} else {
|
||||
println!(
|
||||
"size mismatch name: {} got size: {} expected size: {} path: {:?}",
|
||||
file.name,
|
||||
meta.len(),
|
||||
file.size,
|
||||
path
|
||||
)
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"ERROR in reading file meta name: {} expected size: {} path: {:?}",
|
||||
file.name, file.size, path
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let mut outfile = match File::create(&path).await {
|
||||
Ok(file) => file,
|
||||
Err(e) => panic!("error in creating file: {}", e),
|
||||
};
|
||||
|
||||
println!("downloading {} path = {:?}", file.name, path);
|
||||
match reqwest::get(file.content_download_url.clone()).await {
|
||||
Ok(mut resp) => {
|
||||
while let Some(chunk) = resp.chunk().await? {
|
||||
match outfile.write_all(&chunk).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("error in writing file: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
println!("filename = {} path = {:?}", file.name, path);
|
||||
panic!("Error in downloading: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn generate_directory_structure(
|
||||
share_id: &str,
|
||||
) -> Result<HashMap<String, Node>, reqwest::Error> {
|
||||
let response = get_share_details(share_id).await?;
|
||||
println!("Share Name: {}", response.name);
|
||||
|
||||
let mut tree: HashMap<String, Node> = response
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| {
|
||||
(
|
||||
child.id.clone(),
|
||||
Node::Folder(FolderNode {
|
||||
name: child.name.clone(),
|
||||
childrens: HashMap::new(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for child in response.children {
|
||||
dfs(&mut tree, share_id, child).await;
|
||||
}
|
||||
|
||||
Ok(tree)
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn dfs(map: &mut HashMap<String, Node>, share_id: &str, node: Children) {
|
||||
if node.file.is_some() {
|
||||
map.insert(
|
||||
node.id,
|
||||
Node::File(FileNode {
|
||||
name: node.name,
|
||||
size: node.size,
|
||||
content_download_url: node.content_download_url.unwrap(),
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let child_details = match get_child_details(share_id, &node.id).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
println!("child.name: {} id ={}", node.name, node.id);
|
||||
panic!("error in getting child details: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
let mut mapref = FolderNode {
|
||||
name: node.name,
|
||||
childrens: HashMap::new(),
|
||||
};
|
||||
|
||||
for child in child_details.children {
|
||||
dfs(&mut mapref.childrens, share_id, child).await;
|
||||
}
|
||||
|
||||
map.insert(node.id, Node::Folder(mapref));
|
||||
}
|
||||
|
||||
async fn get_share_details(share_id: &str) -> Result<DriveItem, reqwest::Error> {
|
||||
let response = reqwest::get(format!(
|
||||
"https://api.onedrive.com/v1.0/shares/{}/driveItem/?$expand=children",
|
||||
share_id,
|
||||
))
|
||||
.await?
|
||||
.json::<DriveItem>()
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn get_child_details(share_id: &str, node_id: &str) -> Result<DriveItem, reqwest::Error> {
|
||||
let response = reqwest::get(format!(
|
||||
"https://api.onedrive.com/v1.0/shares/{}/driveItem/items/{}/?$expand=children",
|
||||
share_id, node_id,
|
||||
))
|
||||
.await?
|
||||
.json::<DriveItem>()
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
147
src/types.rs
Normal file
147
src/types.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DriveItem {
|
||||
pub created_by: CreatedBy,
|
||||
pub created_date_time: String,
|
||||
pub c_tag: String,
|
||||
pub e_tag: String,
|
||||
pub id: String,
|
||||
pub last_modified_by: LastModifiedBy,
|
||||
pub last_modified_date_time: String,
|
||||
pub name: String,
|
||||
pub parent_reference: ParentReference,
|
||||
pub size: u64,
|
||||
pub web_url: String,
|
||||
pub file_system_info: FileSystemInfo,
|
||||
pub folder: Folder,
|
||||
pub reactions: Reactions,
|
||||
pub shared: Shared,
|
||||
#[serde(rename = "children@odata.count")]
|
||||
pub children_odata_count: i64,
|
||||
pub children: Vec<Children>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreatedBy {
|
||||
pub application: Option<Application>,
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Application {
|
||||
pub display_name: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct User {
|
||||
pub display_name: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LastModifiedBy {
|
||||
pub application: Option<Application>,
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ParentReference {
|
||||
pub drive_id: String,
|
||||
pub drive_type: String,
|
||||
pub id: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub path: Option<String>,
|
||||
pub share_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileSystemInfo {
|
||||
pub created_date_time: String,
|
||||
pub last_modified_date_time: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Folder {
|
||||
pub child_count: i64,
|
||||
pub folder_view: FolderView,
|
||||
pub folder_type: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FolderView {
|
||||
pub view_type: String,
|
||||
pub sort_by: String,
|
||||
pub sort_order: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Reactions {
|
||||
pub comment_count: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Shared {
|
||||
pub effective_roles: Vec<String>,
|
||||
pub owner: Owner,
|
||||
pub scope: String,
|
||||
pub shared_date_time: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Owner {
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Children {
|
||||
#[serde(rename = "@content.downloadUrl")]
|
||||
pub content_download_url: Option<String>,
|
||||
pub created_by: CreatedBy,
|
||||
pub created_date_time: String,
|
||||
pub c_tag: String,
|
||||
pub e_tag: String,
|
||||
pub id: String,
|
||||
pub last_modified_by: LastModifiedBy,
|
||||
pub last_modified_date_time: String,
|
||||
pub name: String,
|
||||
pub parent_reference: ParentReference,
|
||||
pub size: u64,
|
||||
pub web_url: String,
|
||||
pub file_system_info: FileSystemInfo,
|
||||
pub folder: Option<Folder>,
|
||||
pub file: Option<File>,
|
||||
pub reactions: Reactions,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct File {
|
||||
pub hashes: Hashes,
|
||||
pub mime_type: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Hashes {
|
||||
pub quick_xor_hash: String,
|
||||
#[serde(rename = "sha1Hash")]
|
||||
pub sha1hash: String,
|
||||
#[serde(rename = "sha256Hash")]
|
||||
pub sha256hash: String,
|
||||
}
|
Loading…
Reference in New Issue
Block a user