mirror of
https://github.com/OMGeeky/drive_syncer.git
synced 2025-12-27 06:29:38 +01:00
change up a lot of things to make writing to drive work pretty well (conflicts not so well)
This commit is contained in:
@@ -27,3 +27,5 @@ ignore = "0.4.20"
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = "0.3.17"
|
||||
console-subscriber = "0.1.9"
|
||||
bimap = "0.6.3"
|
||||
md-5 = "0.10"
|
||||
|
||||
@@ -27,7 +27,7 @@ pub trait CommonEntry {
|
||||
// ) -> Self;
|
||||
}
|
||||
#[async_trait]
|
||||
pub trait CommonFilesystem<Entry: CommonEntry> {
|
||||
pub trait CommonFilesystem<Entry: CommonEntry > {
|
||||
fn get_entries(&self) -> &HashMap<Inode, Entry>;
|
||||
fn get_entries_mut(&mut self) -> &mut HashMap<Inode, Entry>;
|
||||
fn get_children(&self) -> &HashMap<Inode, Vec<Inode>>;
|
||||
@@ -135,13 +135,13 @@ pub trait CommonFilesystem<Entry: CommonEntry> {
|
||||
debug!("add_file_entry: {}:{:?}; {}", parent, name, mode);
|
||||
|
||||
let ino = self
|
||||
.add_entry(name, mode, FileType::RegularFile, parent, size)
|
||||
.add_entry_new(name, mode, FileType::RegularFile, parent, size)
|
||||
.await?;
|
||||
|
||||
Ok(ino)
|
||||
}
|
||||
|
||||
async fn add_entry(
|
||||
async fn add_entry_new(
|
||||
&mut self,
|
||||
name: &OsStr,
|
||||
mode: u16,
|
||||
@@ -150,6 +150,21 @@ pub trait CommonFilesystem<Entry: CommonEntry> {
|
||||
size: u64,
|
||||
) -> Result<Inode>;
|
||||
|
||||
fn add_entry(
|
||||
&mut self,
|
||||
entry: Entry,
|
||||
parent_ino: impl Into<Inode> + Debug,
|
||||
) -> Inode
|
||||
where Entry: Debug{
|
||||
let ino = entry.get_ino();
|
||||
self.get_entries_mut().insert(
|
||||
ino,entry,
|
||||
);
|
||||
|
||||
self.add_child(parent_ino, &ino);
|
||||
ino
|
||||
}
|
||||
|
||||
fn add_child(&mut self, parent_ino: impl Into<Inode>, ino: impl Into<Inode>) {
|
||||
let parents_child_list = self
|
||||
.get_children_mut()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use tracing::{error, instrument};
|
||||
use anyhow::{anyhow, Context};
|
||||
use drive3::api::{Drive, File};
|
||||
use drive3::chrono::{DateTime, Utc};
|
||||
@@ -39,10 +40,16 @@ pub struct Change {
|
||||
|
||||
impl TryFrom<DriveChange> for Change {
|
||||
type Error = anyhow::Error;
|
||||
#[instrument]
|
||||
fn try_from(drive_change: DriveChange) -> anyhow::Result<Self> {
|
||||
let removed = drive_change.removed.unwrap_or(false);
|
||||
let drive_id = drive_change.file_id.context("drive_id is missing");
|
||||
if let Err(e) = drive_id {
|
||||
error!("drive_id is missing: {:?}", e);
|
||||
return Err(anyhow!("drive_id is missing: {:?}", e));
|
||||
}
|
||||
Ok(Self {
|
||||
drive_id: DriveId::from(drive_change.drive_id.context("drive_id is missing")?),
|
||||
drive_id: DriveId::from(drive_id?),
|
||||
kind: ChangeType::from_drive_change(drive_change.change_type, drive_change.file, drive_change.drive, removed)?,
|
||||
time: drive_change.time.context("time is missing")?,
|
||||
removed,
|
||||
|
||||
@@ -3,6 +3,9 @@ use crate::fs::{CommonEntry, Inode};
|
||||
use crate::google_drive::DriveId;
|
||||
use fuser::FileAttr;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::PathBuf;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DriveEntry {
|
||||
pub ino: Inode,
|
||||
@@ -10,49 +13,70 @@ pub struct DriveEntry {
|
||||
|
||||
pub name: OsString,
|
||||
// pub drive_path: OsString,
|
||||
pub local_path: LocalPath,
|
||||
pub local_path: Option<LocalPath>,
|
||||
pub attr: FileAttr,
|
||||
pub drive_metadata: Option<drive3::api::File>,
|
||||
pub has_upstream_content_changes: bool,
|
||||
pub md5_checksum: Option<String>,
|
||||
pub local_md5_checksum: Option<String>,
|
||||
}
|
||||
|
||||
impl DriveEntry {
|
||||
#[instrument]
|
||||
pub(crate) fn set_md5_checksum(&mut self, md5_checksum: Option<String>) {
|
||||
self.md5_checksum = md5_checksum.clone();
|
||||
self.local_md5_checksum = md5_checksum;
|
||||
}
|
||||
}
|
||||
|
||||
impl DriveEntry {
|
||||
pub fn new(
|
||||
ino: impl Into<Inode>,
|
||||
name: impl Into<OsString>,
|
||||
drive_id: impl Into<DriveId>,
|
||||
|
||||
local_path: impl Into<LocalPath>,
|
||||
// local_path: impl Into<LocalPath>,
|
||||
attr: FileAttr,
|
||||
drive_metadata: Option<drive3::api::File>,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
let path = local_path.into();
|
||||
// let path = local_path.into();
|
||||
Self {
|
||||
ino: ino.into(),
|
||||
drive_id: drive_id.into(),
|
||||
name,
|
||||
// drive_path: path.clone().into(),
|
||||
local_path: path,
|
||||
local_path: None,
|
||||
attr,
|
||||
drive_metadata,
|
||||
has_upstream_content_changes: true,
|
||||
md5_checksum:None,
|
||||
local_md5_checksum:None,
|
||||
}
|
||||
}
|
||||
pub fn build_local_path(&mut self, parent: Option<LocalPath>){
|
||||
if let Some(parent_path) = parent {
|
||||
let path = parent_path.join(&self.name);
|
||||
self.local_path = Some(LocalPath::from(path));
|
||||
}else{
|
||||
self.local_path = Some(LocalPath::from(PathBuf::from("")));
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CommonEntry for DriveEntry {
|
||||
fn get_ino(&self) -> Inode {
|
||||
self.ino
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &OsStr {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn get_local_path(&self) -> &LocalPath {
|
||||
&self.local_path
|
||||
}
|
||||
|
||||
fn get_attr(&self) -> &FileAttr {
|
||||
&self.attr
|
||||
}
|
||||
}
|
||||
// impl CommonEntry for DriveEntry {
|
||||
// fn get_ino(&self) -> Inode {
|
||||
// self.ino
|
||||
// }
|
||||
//
|
||||
// fn get_name(&self) -> &OsStr {
|
||||
// &self.name
|
||||
// }
|
||||
//
|
||||
// fn get_local_path(&self) -> &LocalPath {
|
||||
// &self.local_path
|
||||
// }
|
||||
//
|
||||
// fn get_attr(&self) -> &FileAttr {
|
||||
// &self.attr
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -153,7 +153,7 @@ impl<'a> DriveFileUploader {
|
||||
self.running_uploads.remove(drive_id);
|
||||
}
|
||||
}
|
||||
#[instrument(skip(file_metadata))]
|
||||
#[instrument(skip(file_metadata, rc), fields(drive=%drive))]
|
||||
async fn upload_file(drive: GoogleDrive,
|
||||
file_metadata: drive3::api::File,
|
||||
local_path: PathBuf,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -152,7 +152,7 @@ impl CommonFilesystem<SampleEntry> for SampleFilesystem {
|
||||
fn get_root_path(&self) -> LocalPath {
|
||||
self.source.clone().into()
|
||||
}
|
||||
async fn add_entry(
|
||||
async fn add_entry_new(
|
||||
&mut self,
|
||||
name: &OsStr,
|
||||
mode: u16,
|
||||
@@ -202,7 +202,7 @@ impl SampleFilesystem {
|
||||
ino = parent_ino;
|
||||
} else {
|
||||
ino = self
|
||||
.add_entry(
|
||||
.add_entry_new(
|
||||
folder_path.file_name().unwrap(),
|
||||
/*TODO: correct permissions*/
|
||||
0o755,
|
||||
|
||||
@@ -28,11 +28,88 @@ use tracing::field::debug;
|
||||
use crate::google_drive::{drive, DriveId, helpers};
|
||||
use crate::prelude::*;
|
||||
|
||||
const FIELDS_FILE: &str = "id, name, size, mimeType, kind, md5Checksum, parents,trashed, createdTime, modifiedTime, viewedByMeTime";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GoogleDrive {
|
||||
hub: DriveHub<HttpsConnector<HttpConnector>>,
|
||||
}
|
||||
|
||||
impl GoogleDrive {
|
||||
#[instrument]
|
||||
pub(crate) async fn list_all_files(&self) -> anyhow::Result<Vec<File>> {
|
||||
let mut files = Vec::new();
|
||||
let mut page_token: Option<String> = None;
|
||||
loop {
|
||||
debug!("list_files: page_token: {:?}", page_token);
|
||||
let mut request =
|
||||
self
|
||||
.hub
|
||||
.files()
|
||||
.list()
|
||||
.param(
|
||||
"fields",
|
||||
&format!("nextPageToken, files({})", FIELDS_FILE),
|
||||
);
|
||||
if let Some(page_token) = page_token {
|
||||
request = request.page_token(&page_token);
|
||||
}
|
||||
let (response, result) = request
|
||||
.doit()
|
||||
.await?;
|
||||
let result_files = result.files.ok_or(anyhow!("no file list returned"))?;
|
||||
debug!("list_files: response: {:?}", result_files.len());
|
||||
files.extend(result_files);
|
||||
page_token = result.next_page_token;
|
||||
if page_token.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(files)
|
||||
}
|
||||
//
|
||||
// #[instrument]
|
||||
// pub async fn list_files(&self, folder_id: DriveId) -> anyhow::Result<Vec<File>> {
|
||||
// debug!("list_files: folder_id: {:?}", folder_id);
|
||||
// let folder_id: OsString = folder_id.into();
|
||||
// let folder_id = match folder_id.into_string() {
|
||||
// Ok(folder_id) => folder_id,
|
||||
// Err(_) => return Err(anyhow!("invalid folder_id")),
|
||||
// };
|
||||
// if folder_id.is_empty() {
|
||||
// return Err(anyhow!("folder_id is empty"));
|
||||
// }
|
||||
// if folder_id.contains('\'') {
|
||||
// return Err(anyhow!("folder_id contains invalid character"));
|
||||
// }
|
||||
// let mut files = Vec::new();
|
||||
// let mut page_token = None;
|
||||
// loop {
|
||||
// debug!("list_files: page_token: {:?}", page_token);
|
||||
// let (response, result) = self
|
||||
// .hub
|
||||
// .files()
|
||||
// .list()
|
||||
// .param(
|
||||
// "fields",
|
||||
// &format!("nextPageToken, files({})", FIELDS_FILE),
|
||||
// )
|
||||
// // .page_token(page_token.as_ref().map(String::as_str))
|
||||
// .q(format!("'{}' in parents", folder_id).as_str())
|
||||
// .doit()
|
||||
// .await?;
|
||||
// let result_files = result.files.ok_or(anyhow!("no file list returned"))?;
|
||||
// debug!("list_files: response: {:?}", result_files.len());
|
||||
// files.extend(result_files);
|
||||
// page_token = result.next_page_token;
|
||||
// if page_token.is_none() {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// Ok(files)
|
||||
// }
|
||||
}
|
||||
|
||||
impl GoogleDrive {
|
||||
#[instrument]
|
||||
pub(crate) async fn get_start_page_token(&self) -> anyhow::Result<StartPageToken> {
|
||||
@@ -48,20 +125,28 @@ impl GoogleDrive {
|
||||
let mut page_token: Option<String> = None;
|
||||
loop {
|
||||
debug!("getting changes since {:?} page: {:?}", start_page_token, page_token);
|
||||
let file_spec = &format!("file({})", FIELDS_FILE);
|
||||
let mut request = self
|
||||
.hub
|
||||
.changes()
|
||||
.list(&start_page_token
|
||||
.start_page_token
|
||||
.as_ref()
|
||||
.context("no start_page_token")?);
|
||||
.context("no start_page_token")?)
|
||||
.param("fields", &format!("changes({}, changeType, removed, fileId, \
|
||||
driveId, drive, time), newStartPageToken, nextPageToken", file_spec));
|
||||
if let Some(page_token) = &page_token {
|
||||
request = request.page_token(page_token);
|
||||
}
|
||||
let (_response, change_list) = request
|
||||
let response = request
|
||||
.doit()
|
||||
.await
|
||||
.context("could not get changes")?;
|
||||
.context("could not get changes");
|
||||
if let Err(e) = &response {
|
||||
error!("error getting changes: {:?}", e);
|
||||
return Err(anyhow!("error getting changes: {:?}", e));
|
||||
}
|
||||
let (_response, change_list) = response?;
|
||||
if let Some(change_list) = change_list.changes {
|
||||
changes.extend(change_list);
|
||||
}
|
||||
@@ -82,12 +167,12 @@ impl GoogleDrive {
|
||||
impl GoogleDrive {
|
||||
#[instrument]
|
||||
pub(crate) async fn get_metadata_for_file(&self, drive_id: DriveId) -> anyhow::Result<File> {
|
||||
let drive_id = drive_id.into_string().map_err(|_| anyhow!("invalid drive_id"))?;
|
||||
let drive_id = drive_id.to_string();
|
||||
let (response, file) = self
|
||||
.hub
|
||||
.files()
|
||||
.get(&drive_id)
|
||||
.param("fields", "id, name, modifiedTime, driveId, size, createdTime, viewedByMeTime")
|
||||
.param("fields", &FIELDS_FILE)
|
||||
.doit().await?;
|
||||
|
||||
Ok(file)
|
||||
@@ -120,10 +205,7 @@ impl GoogleDrive {
|
||||
file_id,
|
||||
target_file.display()
|
||||
);
|
||||
let file_id: String = match file_id.try_into() {
|
||||
Ok(file_id) => file_id,
|
||||
Err(e) => return Err(anyhow!("invalid file_id: {:?}", e).into()),
|
||||
};
|
||||
let file_id: String = file_id.to_string();
|
||||
|
||||
let file = download_file_by_id(&self, file_id, target_file.as_path()).await;
|
||||
debug!("download_file: completed");
|
||||
@@ -245,7 +327,7 @@ impl GoogleDrive {
|
||||
.list()
|
||||
.param(
|
||||
"fields",
|
||||
"nextPageToken, files(id, name, size, mimeType, kind)",
|
||||
&format!("nextPageToken, files({})", FIELDS_FILE),
|
||||
)
|
||||
// .page_token(page_token.as_ref().map(String::as_str))
|
||||
.q(format!("'{}' in parents", folder_id).as_str())
|
||||
@@ -318,10 +400,11 @@ async fn download_file_by_id(
|
||||
) -> Result<File> {
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
let id = id.into();
|
||||
let (response, content): (Response<Body>, google_drive3::api::File) = hub
|
||||
.hub
|
||||
.files()
|
||||
.get(&id.into())
|
||||
.get(&id)
|
||||
.add_scope(Scope::Readonly)
|
||||
.acknowledge_abuse(true)
|
||||
.param("alt", "media")
|
||||
@@ -331,8 +414,17 @@ async fn download_file_by_id(
|
||||
debug!("download_file_by_id(): response: {:?}", response);
|
||||
debug!("download_file_by_id(): content: {:?}", content);
|
||||
write_body_to_file(response, target_path).await?;
|
||||
let (_, file) = hub
|
||||
.hub
|
||||
.files()
|
||||
.get(&id)
|
||||
.add_scope(Scope::Readonly)
|
||||
.param("fields", FIELDS_FILE)
|
||||
.doit()
|
||||
.await?;
|
||||
debug!("download_file_by_id(): file: {:?}", file);
|
||||
|
||||
Ok(content)
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
async fn write_body_to_file(response: Response<Body>, target_path: &Path) -> Result<()> {
|
||||
@@ -477,7 +569,7 @@ async fn update_file_content_on_drive(
|
||||
.hub
|
||||
.files()
|
||||
.update(file, &id)
|
||||
.upload(stream, mime_type)
|
||||
.upload_resumable(stream, mime_type)
|
||||
.await?;
|
||||
debug!("upload done!");
|
||||
debug!("update_file_on_drive(): response: {:?}", response);
|
||||
|
||||
@@ -1,49 +1,69 @@
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::{Display, Pointer};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DriveId(OsString);
|
||||
pub struct DriveId(String);
|
||||
|
||||
impl DriveId {
|
||||
pub(crate) fn root() -> DriveId {
|
||||
DriveId(OsString::from("root"))
|
||||
DriveId(String::from("root"))
|
||||
}
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
self.0.to_str()
|
||||
}
|
||||
pub fn into_string(self) -> Result<String, OsString> {
|
||||
self.0.into_string()
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<OsString> for DriveId {
|
||||
fn into(self) -> OsString {
|
||||
self.0
|
||||
OsString::from(self.0)
|
||||
}
|
||||
}
|
||||
impl TryInto<String> for DriveId {
|
||||
type Error = OsString;
|
||||
|
||||
fn try_into(self) -> Result<String, Self::Error> {
|
||||
self.0.into_string()
|
||||
}
|
||||
}
|
||||
impl From<OsString> for DriveId {
|
||||
fn from(value: OsString) -> Self {
|
||||
DriveId(value)
|
||||
impl TryFrom<OsString> for DriveId {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: OsString) -> anyhow::Result<Self> {
|
||||
let result = value.into_string();
|
||||
if let Err(e) = result {
|
||||
return Err(anyhow::anyhow!("Failed to convert OsString to String: {:?}", e));
|
||||
}
|
||||
Ok(DriveId::new(result.unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for DriveId {
|
||||
fn from(value: String) -> Self {
|
||||
OsString::from(value).into()
|
||||
DriveId::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for DriveId {
|
||||
fn from(s: &str) -> Self {
|
||||
DriveId(OsString::from(s))
|
||||
DriveId::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DriveId> for DriveId {
|
||||
fn from(s: &DriveId) -> Self {
|
||||
s.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for DriveId {
|
||||
fn from(s: &String) -> Self {
|
||||
DriveId::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl DriveId {
|
||||
pub fn new(id: impl Into<OsString>) -> Self {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self(id.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DriveId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,38 +13,38 @@ pub fn get_mime_from_file_metadata(file: &File) -> anyhow::Result<Mime> {
|
||||
&file.mime_type.as_ref().unwrap_or(&"*/*".to_string()),
|
||||
)?)
|
||||
}
|
||||
pub fn get_drive_id_from_local_path(drive: &DriveFilesystem, path: &Path) -> Result<DriveId> {
|
||||
let drive_mount_point: &PathBuf = &drive.get_root_path().into();
|
||||
debug!("get_drive_id_from_path(): (0) path: '{}'", path.display());
|
||||
let path = match path.strip_prefix(drive_mount_point) {
|
||||
Err(e) => {
|
||||
return Err(anyhow!(
|
||||
"Path {:?} is not a prefix of {:?}",
|
||||
drive_mount_point,
|
||||
path
|
||||
))?
|
||||
}
|
||||
Ok(path) => path,
|
||||
};
|
||||
debug!("get_drive_id_from_path(): (1) path: '{}'", path.display());
|
||||
if path == Path::new("/") || path == Path::new("") {
|
||||
debug!(
|
||||
"get_drive_id_from_path(): (1) path is root: '{}'",
|
||||
path.display()
|
||||
);
|
||||
return Ok("root".into());
|
||||
}
|
||||
|
||||
let mut parent_ino: Inode = 5u32.into();
|
||||
// let mut parent_ino : Inode =Inode::from(5u32);//.into();
|
||||
for part in path.iter() {
|
||||
debug!("get_drive_id_from_path(): (2..) path: '{:?}'", part);
|
||||
|
||||
let children = drive.get_children().get(&parent_ino);
|
||||
debug!("get_drive_id_from_path(): (2..) children: '{:?}'", children);
|
||||
}
|
||||
todo!("get_drive_id_from_path()")
|
||||
}
|
||||
// pub fn get_drive_id_from_local_path(drive: &DriveFilesystem, path: &Path) -> Result<DriveId> {
|
||||
// let drive_mount_point: &PathBuf = &drive.get_root_path().into();
|
||||
// debug!("get_drive_id_from_path(): (0) path: '{}'", path.display());
|
||||
// let path = match path.strip_prefix(drive_mount_point) {
|
||||
// Err(e) => {
|
||||
// return Err(anyhow!(
|
||||
// "Path {:?} is not a prefix of {:?}",
|
||||
// drive_mount_point,
|
||||
// path
|
||||
// ))?
|
||||
// }
|
||||
// Ok(path) => path,
|
||||
// };
|
||||
// debug!("get_drive_id_from_path(): (1) path: '{}'", path.display());
|
||||
// if path == Path::new("/") || path == Path::new("") {
|
||||
// debug!(
|
||||
// "get_drive_id_from_path(): (1) path is root: '{}'",
|
||||
// path.display()
|
||||
// );
|
||||
// return Ok("root".into());
|
||||
// }
|
||||
//
|
||||
// let mut parent_ino: Inode = 5u32.into();
|
||||
// // let mut parent_ino : Inode =Inode::from(5u32);//.into();
|
||||
// for part in path.iter() {
|
||||
// debug!("get_drive_id_from_path(): (2..) path: '{:?}'", part);
|
||||
//
|
||||
// let children = drive.get_children().get(&parent_ino);
|
||||
// debug!("get_drive_id_from_path(): (2..) children: '{:?}'", children);
|
||||
// }
|
||||
// todo!("get_drive_id_from_path()")
|
||||
// }
|
||||
mod test {
|
||||
use super::*;
|
||||
// #[tokio::test]
|
||||
|
||||
408
src/lib.rs
408
src/lib.rs
@@ -1,27 +1,23 @@
|
||||
#![allow(dead_code, unused)]
|
||||
// #![allow(dead_code, unused)]
|
||||
|
||||
extern crate google_drive3 as drive3;
|
||||
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use drive3::{DriveHub, hyper, hyper_rustls, oauth2};
|
||||
use drive3::api::Channel;
|
||||
use fuser::{FileAttr, Filesystem, FileType, FUSE_ROOT_ID, MountOption, ReplyAttr, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyWrite, ReplyXattr, Request, Session, SessionUnmounter, TimeOrNow};
|
||||
use google_drive3::oauth2::read_application_secret;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration};
|
||||
|
||||
|
||||
|
||||
|
||||
use fuser::{Session, SessionUnmounter,MountOption};
|
||||
// use nix;
|
||||
use notify::{INotifyWatcher, recommended_watcher, RecommendedWatcher};
|
||||
use tempfile::TempDir;
|
||||
use tokio::io::{AsyncReadExt, stdin};
|
||||
use tokio::runtime::Runtime;
|
||||
// use tokio::io::{AsyncReadExt, stdin};
|
||||
// use tokio::runtime::Runtime;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::{channel, Sender};
|
||||
use tokio::sync::mpsc::{ Sender};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use prelude::*;
|
||||
|
||||
@@ -37,378 +33,6 @@ pub mod google_drive;
|
||||
pub mod prelude;
|
||||
pub mod config;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn init_logger() {
|
||||
todo!("init logger (tracing)")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn does_it_work() {
|
||||
init_logger();
|
||||
list_files().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sample() -> Result<()> {
|
||||
//Test file id: "1IotISYu3cF7JrOdfFPKNOkgYg1-ii5Qs"
|
||||
list_files().await
|
||||
}
|
||||
|
||||
async fn list_files() -> Result<()> {
|
||||
debug!("Hello, world!");
|
||||
let secret: oauth2::ApplicationSecret = read_application_secret("auth/client_secret.json")
|
||||
.await
|
||||
.expect("failed to read client secret file");
|
||||
let auth = oauth2::InstalledFlowAuthenticator::builder(
|
||||
secret,
|
||||
oauth2::InstalledFlowReturnMethod::HTTPRedirect,
|
||||
)
|
||||
.persist_tokens_to_disk("auth/token_store.json")
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let hub = DriveHub::new(
|
||||
hyper::Client::builder().build(
|
||||
hyper_rustls::HttpsConnectorBuilder::new()
|
||||
.with_native_roots()
|
||||
.https_or_http()
|
||||
.enable_http1()
|
||||
.enable_http2()
|
||||
.build(),
|
||||
),
|
||||
auth,
|
||||
);
|
||||
|
||||
let result = hub
|
||||
.files()
|
||||
.get("1IotISYu3cF7JrOdfFPKNOkgYg1-ii5Qs")
|
||||
.doit()
|
||||
.await?;
|
||||
// debug!("Result: {:?}", result);
|
||||
let (body, file) = result;
|
||||
|
||||
debug!("Body: {:?}", body);
|
||||
debug!("File: {:?}", file);
|
||||
|
||||
// let result = hub.files().list().corpus("user").doit().await;
|
||||
|
||||
// debug!("Result: {:?}", result);
|
||||
info!("Filename: {:?}", file.name.unwrap_or("NO NAME".to_string()));
|
||||
info!(
|
||||
"Description: {:?}",
|
||||
file.description.unwrap_or("NO DESCRIPTION".to_string())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyFS {
|
||||
/// how long the responses can/should be cached
|
||||
time_to_live: Duration,
|
||||
|
||||
main_ino: u64,
|
||||
main_size: u64,
|
||||
main_blksize: u64,
|
||||
main_uid: u32,
|
||||
main_gid: u32,
|
||||
main_flags: u32,
|
||||
main_content: Vec<u8>,
|
||||
main_file_type: Option<FileType>,
|
||||
main_name: String,
|
||||
}
|
||||
|
||||
struct DirEntry {
|
||||
ino: u64,
|
||||
name: String,
|
||||
file_type: FileType,
|
||||
}
|
||||
|
||||
impl MyFS {
|
||||
fn get_attr(&self, ino: u64) -> Option<FileAttr> {
|
||||
// Get the file attributes based on the inode number
|
||||
if ino == FUSE_ROOT_ID {
|
||||
Some(FileAttr {
|
||||
ino: FUSE_ROOT_ID,
|
||||
size: 0,
|
||||
blocks: 0,
|
||||
atime: UNIX_EPOCH,
|
||||
mtime: UNIX_EPOCH,
|
||||
ctime: UNIX_EPOCH,
|
||||
crtime: UNIX_EPOCH,
|
||||
kind: FileType::Directory,
|
||||
perm: 0o755,
|
||||
nlink: 0,
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
rdev: 0,
|
||||
blksize: 0,
|
||||
flags: 0,
|
||||
})
|
||||
} else if ino == self.main_ino {
|
||||
Some(FileAttr {
|
||||
ino: FUSE_ROOT_ID,
|
||||
size: self.main_size,
|
||||
blocks: 0,
|
||||
atime: UNIX_EPOCH,
|
||||
mtime: UNIX_EPOCH,
|
||||
ctime: UNIX_EPOCH,
|
||||
crtime: UNIX_EPOCH,
|
||||
kind: self.main_file_type.unwrap_or(FileType::RegularFile),
|
||||
perm: 0o755,
|
||||
nlink: 0,
|
||||
uid: self.main_uid,
|
||||
gid: self.main_gid,
|
||||
rdev: 0,
|
||||
blksize: self.main_blksize as u32,
|
||||
flags: self.main_flags,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_attr(
|
||||
&mut self,
|
||||
ino: u64,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
flags: Option<u32>,
|
||||
) -> Option<FileAttr> {
|
||||
debug!(
|
||||
"set_attr=> ino: {}; mode: {:?}; uid: {:?}; gid: {:?}; size: {:?}; flags: {:?}",
|
||||
ino, mode, uid, gid, size, flags
|
||||
);
|
||||
// Get the file attributes based on the inode number
|
||||
if ino == self.main_ino {
|
||||
self.main_size = size.unwrap_or(self.main_size);
|
||||
self.main_flags = flags.unwrap_or(self.main_flags);
|
||||
self.main_uid = uid.unwrap_or(self.main_uid);
|
||||
self.main_gid = gid.unwrap_or(self.main_gid);
|
||||
return self.get_attr(ino);
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn write_file(
|
||||
&mut self,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
offset: i64,
|
||||
data: &[u8],
|
||||
flags: i32,
|
||||
) -> Option<usize> {
|
||||
// Write the file and reply with the number of bytes written
|
||||
debug!(
|
||||
"write_file=> ino: {}; fh: {}; offset: {}; data: {:?}; flags: {}",
|
||||
ino, fh, offset, data, flags
|
||||
);
|
||||
if ino == self.main_ino {
|
||||
self.main_content = data.to_vec();
|
||||
// todo!("write the file and reply with the number of bytes written");
|
||||
return Some(data.len());
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn read_file(&self, ino: u64, fh: u64, offset: i64, size: u32) -> Option<Vec<u8>> {
|
||||
debug!(
|
||||
"read_file=> ino: {}; fh: {}; offset: {}; size: {}",
|
||||
ino, fh, offset, size
|
||||
);
|
||||
if ino == self.main_ino {
|
||||
// Read the file and reply with the data
|
||||
let data = &self.main_content.clone(); //b"Hello World!";
|
||||
let offset_usize = offset as usize;
|
||||
let size_usize = size as usize;
|
||||
if data.len() <= offset_usize {
|
||||
let result = vec![libc::EOF as u8];
|
||||
debug!("read_file=> (0) result: {:?}", result);
|
||||
return Some(result);
|
||||
}
|
||||
if offset_usize + size_usize > data.len() {
|
||||
//return the rest of the data + EOF
|
||||
let mut result = data[1..].to_vec();
|
||||
result.push(libc::EOF as u8);
|
||||
debug!("read_file=> (1) result: {:?}", result);
|
||||
return Some(result);
|
||||
// todo!("output the rest of the data + EOF, not just EOF");
|
||||
return None;
|
||||
}
|
||||
|
||||
let result = data[offset_usize..offset_usize + size_usize].to_vec();
|
||||
debug!("read_file=> (2) result: {:?}", result);
|
||||
return Some(result);
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn read_dir(&self, ino: u64) -> Option<Vec<DirEntry>> {
|
||||
if ino == FUSE_ROOT_ID {
|
||||
let mut entries = Vec::new();
|
||||
|
||||
let dir_entry = DirEntry {
|
||||
ino: self.main_ino,
|
||||
name: self.main_name.clone(),
|
||||
file_type: self.main_file_type.unwrap_or(FileType::RegularFile),
|
||||
};
|
||||
|
||||
entries.push(dir_entry);
|
||||
Some(entries)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Filesystem for MyFS {
|
||||
fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) {
|
||||
if _ino == self.main_ino {
|
||||
reply.opened(0, 0);
|
||||
} else {
|
||||
reply.error(libc::ENOENT);
|
||||
}
|
||||
}
|
||||
fn access(&mut self, _req: &Request<'_>, ino: u64, mask: i32, reply: ReplyEmpty) {
|
||||
if ino == self.main_ino {
|
||||
reply.ok()
|
||||
} else {
|
||||
reply.error(libc::ENOENT)
|
||||
}
|
||||
}
|
||||
fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
|
||||
if let Some(attr) = self.get_attr(ino) {
|
||||
reply.attr(&self.time_to_live, &attr);
|
||||
} else {
|
||||
reply.error(libc::ENOENT);
|
||||
}
|
||||
}
|
||||
fn write(
|
||||
&mut self,
|
||||
_req: &Request<'_>,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
offset: i64,
|
||||
data: &[u8],
|
||||
write_flags: u32,
|
||||
flags: i32,
|
||||
lock_owner: Option<u64>,
|
||||
reply: ReplyWrite,
|
||||
) {
|
||||
if let Some(size) = self.write_file(ino, fh, offset, data, flags) {
|
||||
reply.written(size as u32);
|
||||
} else {
|
||||
reply.error(libc::ENOENT);
|
||||
}
|
||||
}
|
||||
fn setattr(
|
||||
&mut self,
|
||||
_req: &Request<'_>,
|
||||
ino: u64,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
_atime: Option<TimeOrNow>,
|
||||
_mtime: Option<TimeOrNow>,
|
||||
_ctime: Option<SystemTime>,
|
||||
fh: Option<u64>,
|
||||
_crtime: Option<SystemTime>,
|
||||
_chgtime: Option<SystemTime>,
|
||||
_bkuptime: Option<SystemTime>,
|
||||
flags: Option<u32>,
|
||||
reply: ReplyAttr,
|
||||
) {
|
||||
if let Some(attr) = self.set_attr(ino, mode, uid, gid, size, flags) {
|
||||
reply.attr(&self.time_to_live, &attr);
|
||||
} else {
|
||||
reply.error(libc::ENOENT);
|
||||
}
|
||||
}
|
||||
fn read(
|
||||
&mut self,
|
||||
_req: &Request<'_>,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
offset: i64,
|
||||
size: u32,
|
||||
flags: i32,
|
||||
lock_owner: Option<u64>,
|
||||
reply: ReplyData,
|
||||
) {
|
||||
if let Some(data) = self.read_file(ino, fh, offset, size) {
|
||||
let data = data.as_slice();
|
||||
reply.data(data);
|
||||
} else {
|
||||
reply.error(libc::ENOENT);
|
||||
}
|
||||
}
|
||||
fn readdir(
|
||||
&mut self,
|
||||
_req: &Request<'_>,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
offset: i64,
|
||||
mut reply: ReplyDirectory,
|
||||
) {
|
||||
debug!("readdir=> ino: {}; fh: {}; offset: {}", ino, fh, offset);
|
||||
if let Some(entries) = self.read_dir(ino) {
|
||||
for (i, entry) in entries.iter().enumerate().skip(offset as usize) {
|
||||
if reply.add(entry.ino, (i + 1) as i64, entry.file_type, &entry.name) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
reply.ok();
|
||||
} else {
|
||||
reply.error(libc::ENOENT);
|
||||
}
|
||||
}
|
||||
fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
|
||||
let main_path = OsStr::new(&self.main_name);
|
||||
debug!(
|
||||
"lookup=> parent: {}; name: {:?}; main_path: {:?}",
|
||||
parent, name, main_path
|
||||
);
|
||||
if name.eq_ignore_ascii_case(main_path) {
|
||||
let attr = self.get_attr(self.main_ino).unwrap();
|
||||
reply.entry(&self.time_to_live, &attr, 0);
|
||||
} else {
|
||||
reply.error(libc::ENOENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn watch_file_reading() -> Result<()> {
|
||||
let mountpoint = "/tmp/fuse/1";
|
||||
let options = vec![
|
||||
MountOption::RW,
|
||||
// MountOption::FSName("myfs".to_string()),
|
||||
// MountOption::AllowOther,
|
||||
// MountOption::AutoUnmount,
|
||||
];
|
||||
debug!("Mounting fuse filesystem at {}", mountpoint);
|
||||
fuser::mount2(
|
||||
MyFS {
|
||||
time_to_live: Duration::from_secs(5),
|
||||
main_ino: 2,
|
||||
main_name: "1.txt".to_string(),
|
||||
main_file_type: Some(FileType::RegularFile),
|
||||
main_content: b"Hello World!".to_vec(),
|
||||
..Default::default()
|
||||
},
|
||||
mountpoint,
|
||||
&options,
|
||||
)
|
||||
.unwrap();
|
||||
debug!("Exiting...");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn sample_fs() -> Result<()> {
|
||||
let mountpoint = "/tmp/fuse/1";
|
||||
let source = "/tmp/fuse/2";
|
||||
@@ -425,7 +49,7 @@ pub async fn sample_fs() -> Result<()> {
|
||||
pub async fn sample_drive_fs() -> Result<()> {
|
||||
let mountpoint = "/tmp/fuse/3";
|
||||
let upload_ignore_path = Path::new("config/.upload_ignore");
|
||||
let settings_path = Path::new("config/settings.json");
|
||||
// let settings_path = Path::new("config/settings.json");
|
||||
|
||||
let cache_dir = get_cache_dir()?;
|
||||
let upload_ignore = CommonFileFilter::from_path(upload_ignore_path)?;
|
||||
@@ -484,11 +108,11 @@ async fn mount(fs: DriveFilesystem,
|
||||
let mut session = Session::new(fs, mountpoint.as_ref(), options)?;
|
||||
let session_ender = session.unmount_callable();
|
||||
let end_program_signal_handle = tokio::spawn(async move {
|
||||
end_program_signal_awaiter(sender, session_ender).await;
|
||||
let _ = end_program_signal_awaiter(sender, session_ender).await;
|
||||
});
|
||||
debug!("Mounting fuse filesystem" );
|
||||
session.run();
|
||||
debug!("Finished with mounting");
|
||||
let _ = session.run();
|
||||
debug!("Stopped with mounting");
|
||||
// Ok(session_ender)
|
||||
Ok(end_program_signal_handle)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user