generalbots/botserver/src/basic/keywords/file_ops/copy_move.rs

270 lines
10 KiB
Rust

/*****************************************************************************\
| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® |
| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ |
| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ |
| |
| General Bots Copyright (c) pragmatismo.com.br. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of pragmatismo.com.br. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
use crate::basic::keywords::use_account::{
get_account_credentials, is_account_path, parse_account_path,
};
use crate::core::shared::models::schema::bots::dsl::*;
use crate::core::shared::models::UserSession;
use crate::core::shared::state::AppState;
use diesel::prelude::*;
use log::trace;
use std::error::Error;
use super::basic_io::execute_delete_file;
pub async fn execute_copy(
state: &AppState,
user: &UserSession,
source: &str,
destination: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let source_is_account = is_account_path(source);
let dest_is_account = is_account_path(destination);
if source_is_account || dest_is_account {
return execute_copy_with_account(state, user, source, destination).await;
}
let client = state.drive.as_ref().ok_or("S3 client not configured")?;
let bot_name: String = {
let mut db_conn = state.conn.get().map_err(|e| format!("DB error: {e}"))?;
bots.filter(id.eq(&user.bot_id))
.select(name)
.first(&mut *db_conn)
.map_err(|e| {
log::error!("Failed to query bot name: {e}");
e
})?
};
let bucket_name = format!("{bot_name}.gbai");
let source_key = format!("{bot_name}.gbdrive/{source}");
let dest_key = format!("{bot_name}.gbdrive/{destination}");
client
.copy_object()
.bucket(&bucket_name)
.source(&source_key)
.dest(&dest_key)
.send()
.await
.map_err(|e| format!("S3 copy failed: {e}"))?;
trace!("COPY successful: {source} -> {destination}");
Ok(())
}
pub async fn execute_copy_with_account(
state: &AppState,
user: &UserSession,
source: &str,
destination: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let source_is_account = is_account_path(source);
let dest_is_account = is_account_path(destination);
let content = if source_is_account {
let (email, path) = parse_account_path(source).ok_or("Invalid account:// path format")?;
let creds = get_account_credentials(&state.conn, &email, user.bot_id)
.await
.map_err(|e| format!("Failed to get credentials: {e}"))?;
download_from_account(&creds, &path).await?
} else {
read_from_local(state, user, source).await?
};
if dest_is_account {
let (email, path) =
parse_account_path(destination).ok_or("Invalid account:// path format")?;
let creds = get_account_credentials(&state.conn, &email, user.bot_id)
.await
.map_err(|e| format!("Failed to get credentials: {e}"))?;
upload_to_account(&creds, &path, &content).await?;
} else {
write_to_local(state, user, destination, &content).await?;
}
trace!("COPY with account successful: {source} -> {destination}");
Ok(())
}
pub async fn download_from_account(
creds: &crate::basic::keywords::use_account::AccountCredentials,
path: &str,
) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
let client = reqwest::Client::new();
match creds.provider.as_str() {
"gmail" | "google" => {
let url = format!(
"https://www.googleapis.com/drive/v3/files/{}?alt=media",
urlencoding::encode(path)
);
let resp = client
.get(&url)
.bearer_auth(&creds.access_token)
.send()
.await?;
if !resp.status().is_success() {
return Err(format!("Google Drive download failed: {}", resp.status()).into());
}
Ok(resp.bytes().await?.to_vec())
}
"outlook" | "microsoft" => {
let url = format!(
"https://graph.microsoft.com/v1.0/me/drive/root:/{}:/content",
urlencoding::encode(path)
);
let resp = client
.get(&url)
.bearer_auth(&creds.access_token)
.send()
.await?;
if !resp.status().is_success() {
return Err(format!("OneDrive download failed: {}", resp.status()).into());
}
Ok(resp.bytes().await?.to_vec())
}
_ => Err(format!("Unsupported provider: {}", creds.provider).into()),
}
}
pub async fn upload_to_account(
creds: &crate::basic::keywords::use_account::AccountCredentials,
path: &str,
content: &[u8],
) -> Result<(), Box<dyn Error + Send + Sync>> {
let client = reqwest::Client::new();
match creds.provider.as_str() {
"gmail" | "google" => {
let url = format!(
"https://www.googleapis.com/upload/drive/v3/files?uploadType=media&name={}",
urlencoding::encode(path)
);
let resp = client
.post(&url)
.bearer_auth(&creds.access_token)
.body(content.to_vec())
.send()
.await?;
if !resp.status().is_success() {
return Err(format!("Google Drive upload failed: {}", resp.status()).into());
}
}
"outlook" | "microsoft" => {
let url = format!(
"https://graph.microsoft.com/v1.0/me/drive/root:/{}:/content",
urlencoding::encode(path)
);
let resp = client
.put(&url)
.bearer_auth(&creds.access_token)
.body(content.to_vec())
.send()
.await?;
if !resp.status().is_success() {
return Err(format!("OneDrive upload failed: {}", resp.status()).into());
}
}
_ => return Err(format!("Unsupported provider: {}", creds.provider).into()),
}
Ok(())
}
pub async fn read_from_local(
state: &AppState,
user: &UserSession,
path: &str,
) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
let client = state.drive.as_ref().ok_or("S3 client not configured")?;
let bot_name: String = {
let mut db_conn = state.conn.get()?;
bots.filter(id.eq(&user.bot_id))
.select(name)
.first(&mut *db_conn)?
};
let bucket_name = format!("{bot_name}.gbai");
let key = format!("{bot_name}.gbdrive/{path}");
let bytes = client
.get_object()
.bucket(&bucket_name)
.key(&key)
.send()
.await?
.body
.collect()
.await?
.into_bytes();
Ok(bytes)
}
pub async fn write_to_local(
state: &AppState,
user: &UserSession,
path: &str,
content: &[u8],
) -> Result<(), Box<dyn Error + Send + Sync>> {
let client = state.drive.as_ref().ok_or("S3 client not configured")?;
let bot_name: String = {
let mut db_conn = state.conn.get()?;
bots.filter(id.eq(&user.bot_id))
.select(name)
.first(&mut *db_conn)?
};
let bucket_name = format!("{bot_name}.gbai");
let key = format!("{bot_name}.gbdrive/{path}");
client
.put_object()
.bucket(&bucket_name)
.key(&key)
.body(content.to_vec())
.send()
.await?;
Ok(())
}
pub async fn execute_move(
state: &AppState,
user: &UserSession,
source: &str,
destination: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
execute_copy(state, user, source, destination).await?;
execute_delete_file(state, user, source).await?;
trace!("MOVE successful: {source} -> {destination}");
Ok(())
}