📑
RustでGoogleDriveにアップロードする
目的
ユニークビジョン株式会社 Advent Calendar 2024の12/4の記事です。
Google Driveにファイルをアップロードしたいです。いくつかハマりポイントがありました。
説明
認証
認証にはOAuth認証とサービスアカウント認証があります。どちらでも動きますが、常駐プログラムでずっと動かしたい場合、サービスアカウント認証の方が便利です。
サービスアカウント認証ではGoogleドライブの置きたいフォルダーの共有にサービスアカウントのメールアドレスを設定してコンテンツ管理者に設定してください。これをしないと権限エラーになります。
hyper-rustls
このcrateではdefaultでは他のcrateとコンフリクトしてうまく動きませんでした。
default-features = falseにしてringを追加する必要がありました。
以下の記事を参考にしてください。
Rust rustlsのpanic"no process-level CryptoProvider available -- call CryptoProvider::install_default"
コード
Cargo.toml
[package]
name = "drive2"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1"
hyper-rustls = { version = "0.27.3", features = ["native-tokio", "http1", "tls12", "logging", "ring"], default-features = false }
google-drive3 = "6.0.0"
serde_json = "1.0.132"
thiserror = "2.0.4"
tokio = { version="1.41.1", features=["macros", "rt-multi-thread", "fs"] }
error.rs
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("Invalid: {0}")]
Invalid(String),
#[error("Json: {0}")]
Json(#[from] serde_json::Error),
#[error("IO: {0}")]
IO(#[from] std::io::Error),
#[error("GoogleDrive: {0}")]
GoogleDrive(#[from] google_drive3::Error),
}
google_drive.rs
use std::path::PathBuf;
use google_drive3::{
api::File,
hyper_util::{self, client::legacy::connect::HttpConnector},
yup_oauth2::{self, ServiceAccountKey},
DriveHub,
};
use crate::error::Error;
use hyper_rustls::HttpsConnector;
pub struct GoogleDrive {
hub: DriveHub<HttpsConnector<HttpConnector>>,
}
impl GoogleDrive {
pub async fn new(secret_json: &str) -> Result<Self, Error> {
let secret: ServiceAccountKey = serde_json::from_str(secret_json)?;
let auth = yup_oauth2::ServiceAccountAuthenticator::builder(secret)
.build()
.await?;
let client =
hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
.build(
hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.unwrap()
.https_or_http()
.enable_http1()
.build(),
);
let hub = DriveHub::new(client, auth);
Ok(Self { hub })
}
pub async fn upload_file(
&self,
file_name: &str,
file_path: &PathBuf,
mime_type: &str,
folder_id: &str,
) -> Result<File, Error> {
self.upload(
file_name,
std::fs::File::open(file_path).unwrap(),
mime_type,
folder_id,
)
.await
}
pub async fn upload_string(
&self,
file_name: &str,
content: &str,
mime_type: &str,
folder_id: &str,
) -> Result<File, Error> {
self.upload(
file_name,
std::io::Cursor::new(content),
mime_type,
folder_id,
)
.await
}
pub async fn upload<RS>(
&self,
file_name: &str,
content: RS,
mime_type: &str,
folder_id: &str,
) -> Result<File, Error>
where
RS: std::io::Read + Send + std::io::Seek,
{
let req = File {
name: Some(file_name.to_string()),
mime_type: Some(mime_type.to_string()),
parents: Some(vec![folder_id.to_string()]),
..Default::default()
};
let result = self
.hub
.files()
.create(req)
.supports_all_drives(true)
.upload(content, mime_type.parse().unwrap())
.await?;
Ok(result.1)
}
}
main.rs
use google_drive::GoogleDrive;
pub mod google_drive;
pub mod error;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let secret = tokio::fs::read_to_string("secret.json").await?;
let drive = GoogleDrive::new(&secret).await?;
let folder_id = "xxxxx";
let res= drive
.upload_string(
"test.csv",
"1,2,3\n4,5,6\n7,8,9",
"text/csv",
folder_id,
)
.await?;
println!("{:?}", res);
Ok(())
}
Discussion