diff --git a/gdriver-backend/src/drive.rs b/gdriver-backend/src/drive.rs index 4eff0db..873ff27 100644 --- a/gdriver-backend/src/drive.rs +++ b/gdriver-backend/src/drive.rs @@ -10,6 +10,7 @@ pub struct Drive { tracked_files: HashMap>, pub path_resolver: PathResolver, google_drive: GoogleDrive, + pub offline_mode: bool, } impl Drive { #[instrument()] @@ -18,8 +19,12 @@ impl Drive { tracked_files: HashMap::new(), path_resolver: PathResolver::new(), google_drive: GoogleDrive::new().await?, + offline_mode: false, }) } + pub fn set_offline_mode(&mut self, offline_mode: bool) { + self.offline_mode = offline_mode; + } #[instrument(skip(self))] pub fn get_file_tracking_state(&self, id: &DriveId) -> TrackingState { let file = self.tracked_files.get(id); @@ -28,20 +33,46 @@ impl Drive { None => TrackingState::Untracked, } } + + #[instrument(skip(self))] + pub async fn get_all_file_metas(&mut self) -> Result<()> { + if self.offline_mode { + info!("Offline mode, skipping get_all_file_metas"); + //TODO: load from local storage + return Ok(()); + } + let has_existing_token = self.google_drive.has_local_change_token().await; + if !has_existing_token { + //only get start token & data if we shouldn't have it + self.google_drive.get_change_start_token().await?; + let x = self.google_drive.get_all_file_metas().await?; + dbg!(&x); + } else { + //TODO: get file metas from local storage + } + + Ok(()) + } + #[instrument(skip(self))] pub async fn update(&mut self) -> Result<()> { + if self.offline_mode { + info!("Offline mode, skipping update"); + return Ok(()); + } let changes = self.google_drive.get_changes().await?; if changes.is_empty() { info!("No changes"); return Ok(()); } for change in changes { - dbg!(change); + dbg!(&change); } Err("Not implemented".into()) // Ok(()) //TODO: implement this } + #[instrument(skip(self))] pub async fn ping(&self) -> Result<()> { self.google_drive.ping().await diff --git a/gdriver-backend/src/drive/google_drive.rs b/gdriver-backend/src/drive/google_drive.rs index 5be43d0..a8d123b 100644 --- a/gdriver-backend/src/drive/google_drive.rs +++ b/gdriver-backend/src/drive/google_drive.rs @@ -1,19 +1,55 @@ use crate::prelude::*; +use chrono::{DateTime, Utc}; use const_format::formatcp; use gdriver_common::{ipc::gdriver_service::SETTINGS, prelude::*}; +use google_drive3::api::File; use google_drive3::{ api::{Change, Scope}, hyper::{client::HttpConnector, Client}, hyper_rustls::{self, HttpsConnector}, oauth2, DriveHub, }; +use serde::{Deserialize, Serialize}; use std::any::type_name; use std::fmt::{Debug, Display, Formatter}; use tokio::fs; const FIELDS_FILE: &'static str = "id, name, size, mimeType, kind, md5Checksum, parents, trashed, createdTime, modifiedTime, viewedByMeTime"; +#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Serialize, Deserialize, Clone, Hash)] +pub struct FileData { + pub id: String, + pub name: String, + pub size: Option, + pub mime_type: String, + pub kind: String, + pub md5_checksum: Option, + pub parents: Option>, + pub trashed: Option, + pub created_time: Option>, + pub modified_time: Option>, + pub viewed_by_me_time: Option>, +} + +impl FileData { + fn convert_from_api_file(file: File) -> Self { + Self { + id: file.id.unwrap_or_default(), + name: file.name.unwrap_or_default(), + size: file.size, + mime_type: file.mime_type.unwrap_or_default(), + kind: file.kind.unwrap_or_default(), + md5_checksum: file.md5_checksum, + parents: file.parents, + trashed: file.trashed, + created_time: file.created_time, + modified_time: file.modified_time, + viewed_by_me_time: file.viewed_by_me_time, + } + } +} const FIELDS_CHANGE: &str = formatcp!( - "nextPageToken, newStartPageToken, changes(removed, fileId, changeType, file({FIELDS_FILE}))" + "nextPageToken, newStartPageToken, changes(removed, fileId, changeType, file({}))", + FIELDS_FILE ); #[derive(Clone)] pub struct GoogleDrive { @@ -21,6 +57,43 @@ pub struct GoogleDrive { changes_start_page_token: Option, } +impl GoogleDrive { + #[instrument] + pub(crate) async fn get_all_file_metas(&self) -> Result> { + let mut page_token: Option = None; + let mut files = Vec::new(); + loop { + let (response, body) = self + .hub + .files() + .list() + .supports_all_drives(false) + .spaces("drive") + .page_token(&page_token.unwrap_or_default()) + .param("fields", &format!("nextPageToken, files({})", FIELDS_FILE)) + .doit() + .await?; + page_token = body.next_page_token; + if response.status().is_success() { + files.extend( + body.files + .unwrap_or_default() + .into_iter() + .map(FileData::convert_from_api_file), + ); + } else { + error!("Could not get files: {:?}", response); + return Err("Could not get files".into()); + } + if page_token.is_none() { + info!("No more pages"); + break; + } + } + Ok(files) + } +} + impl GoogleDrive { #[instrument] pub(crate) async fn new() -> Result { @@ -82,7 +155,7 @@ impl GoogleDrive { #[instrument] pub async fn get_changes(&mut self) -> Result> { info!("Getting changes"); - let mut page_token = Some(self.change_start_token().await?); + let mut page_token = Some(self.get_change_start_token().await?); let mut changes = Vec::new(); while let Some(current_page_token) = page_token { info!("Getting changes with page token: {}", current_page_token); @@ -100,7 +173,7 @@ impl GoogleDrive { .doit() .await?; if let Some(token) = body.new_start_page_token { - self.changes_start_page_token = Some(token); + self.set_change_start_token(token).await?; } if response.status().is_success() { changes.extend(body.changes.unwrap_or_default()); @@ -113,19 +186,21 @@ impl GoogleDrive { trace!("Got {} changes", changes.len()); Ok(changes) } + async fn set_change_start_token(&mut self, token: String) -> Result<()> { + info!("Setting start page token: {}", token); + fs::write(SETTINGS.get_changes_file_path(), token.clone()).await?; + self.changes_start_page_token = Some(token); + Ok(()) + } - async fn change_start_token(&mut self) -> Result { + pub async fn get_change_start_token(&mut self) -> Result { Ok(match &self.changes_start_page_token { None => { info!("Getting start page token"); - let token = fs::read_to_string(SETTINGS.get_changes_file_path()) - .await - .unwrap_or_default(); - self.changes_start_page_token = if !token.is_empty() { - Some(token) - } else { - Some(self.get_changes_start_token_from_api().await?) - }; + let has_local_token = self.has_local_change_token().await; + if !has_local_token { + self.update_change_start_token_from_api().await?; + } info!("Got start page token: {:?}", self.changes_start_page_token); self.changes_start_page_token .clone() @@ -137,7 +212,22 @@ impl GoogleDrive { } }) } - async fn get_changes_start_token_from_api(&self) -> Result { + + pub(crate) async fn has_local_change_token(&mut self) -> bool { + !self + .get_local_change_start_token() + .await + .unwrap_or_default() + .is_empty() + } + + pub async fn get_local_change_start_token(&mut self) -> Option { + self.changes_start_page_token = fs::read_to_string(SETTINGS.get_changes_file_path()) + .await + .ok(); + self.changes_start_page_token.clone() + } + async fn update_change_start_token_from_api(&mut self) -> Result<()> { info!("Getting start page token from API"); let (response, body) = self .hub @@ -147,8 +237,9 @@ impl GoogleDrive { .doit() .await?; if response.status().is_success() { - let start_page_token = body.start_page_token.unwrap_or_default(); - Ok(start_page_token) + let token = body.start_page_token.clone().unwrap_or_default(); + self.set_change_start_token(token).await?; + Ok(()) } else { Err("Could not get start page token".into()) } diff --git a/gdriver-backend/src/path_resolver.rs b/gdriver-backend/src/path_resolver.rs index fadbaaa..f6d5ce6 100644 --- a/gdriver-backend/src/path_resolver.rs +++ b/gdriver-backend/src/path_resolver.rs @@ -1,14 +1,16 @@ use crate::drive::Drive; use crate::prelude::*; use gdriver_common::ipc::gdriver_service::ReadDirResult; +use gdriver_common::path_resolve_error::PathResolveError; use gdriver_common::prelude::*; -use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::Path; #[derive(Eq, PartialEq, Debug, Clone)] pub struct PathResolver { + /// A map of children to their parents parents: HashMap>, + /// A map of parents to their children with id, name and type (folder/file/symlink) children: HashMap>, } @@ -19,20 +21,22 @@ impl PathResolver { children: HashMap::new(), } } - pub async fn get_id_from_path(&mut self, path: &Path, drive: &Drive) -> Result { + pub async fn get_id_from_path( + &mut self, + path: &Path, + ) -> StdResult, PathResolveError> { let segments: Vec<_> = path .to_str() .ok_or(PathResolveError::InvalidPath)? .split('/') .collect(); let mut current = ROOT_ID.clone(); - self.update_from_drive(drive).await?; for segment in segments { - current = self - .get_id_from_parent_and_name(segment, ¤t) - .ok_or("path-segment not found")?; + current = self.get_id_from_parent_and_name(segment, ¤t).ok_or( + PathResolveError::Other("path-segment not found".to_string()), + )?; } - return Ok(current); + return Ok(Some(current)); } pub fn get_id_from_parent_and_name(&self, name: &str, parent: &DriveId) -> Option { if let Some(children) = self.children.get(parent) { @@ -46,16 +50,28 @@ impl PathResolver { async fn update_from_drive(&mut self, drive: &Drive) -> Result<()> { todo!() } + /// Add a relationship between a parent and a child pub(crate) fn add_relationship(&mut self, parent: DriveId, entry: ReadDirResult) { - todo!() + match self.parents.get_mut(&entry.id) { + Some(x) => x.push(parent.clone()), + None => { + self.parents.insert(entry.id.clone(), vec![parent.clone()]); + } + }; + match self.children.get_mut(&parent) { + Some(x) => x.push(entry.clone()), + None => { + self.children.insert(parent.clone(), vec![entry.clone()]); + } + } } + /// Remove the relationship between a parent and a child pub(crate) fn remove_relationship(&mut self, parent: DriveId, entry: ReadDirResult) { - todo!() + self.parents + .get_mut(&entry.id) + .map(|x| x.retain(|e| e != &parent)); + self.children + .get_mut(&parent) + .map(|x| x.retain(|e| e.id != entry.id)); } } - -#[derive(Debug, Serialize, Deserialize, thiserror::Error)] -pub enum PathResolveError { - #[error("The path provided was invalid")] - InvalidPath, -} diff --git a/gdriver-backend/src/service.rs b/gdriver-backend/src/service.rs index 7d497cc..13b0e09 100644 --- a/gdriver-backend/src/service.rs +++ b/gdriver-backend/src/service.rs @@ -16,9 +16,14 @@ struct GdriverServer { drive: Arc>, } impl GDriverService for GdriverServer { - // async fn get_settings(self, context: Context) -> StdResult { - // todo!() - // } + async fn set_offline_mode( + self, + _context: Context, + offline_mode: bool, + ) -> StdResult<(), GDriverServiceError> { + self.drive.lock().await.set_offline_mode(offline_mode); + Ok(()) + } async fn get_file_by_name( self, @@ -42,7 +47,12 @@ impl GDriverService for GdriverServer { context: Context, path: PathBuf, ) -> StdResult { - Err(GetFileByPathError::Other) + let mut drive_lock = self.drive.lock().await; + let x = drive_lock.path_resolver.get_id_from_path(&path).await?; + match x { + None => Err(GetFileByPathError::NotFound), + Some(id) => Ok(id), + } } async fn write_local_change( @@ -191,7 +201,7 @@ pub async fn start() -> Result<()> { let config = &CONFIGURATION; info!("Config: {:?}", **config); - let drive = Drive::new().await?; + let mut drive = Drive::new().await?; match drive.ping().await { Ok(_) => { info!("Can reach google drive api."); @@ -201,7 +211,8 @@ pub async fn start() -> Result<()> { return Err(e); } } - let m = Arc::new(Mutex::new(drive)); + drive.get_all_file_metas().await?; + let drive = Arc::new(Mutex::new(drive)); let server_addr = (config.ip, config.port); let mut listener = tarpc::serde_transport::tcp::listen(&server_addr, Json::default).await?; @@ -220,7 +231,7 @@ pub async fn start() -> Result<()> { let c = channel.transport().peer_addr().unwrap(); let server = GdriverServer { socket_address: c, - drive: m.clone(), + drive: drive.clone(), }; channel.execute(server.serve()).for_each(spawn) }) diff --git a/gdriver-client/src/main.rs b/gdriver-client/src/main.rs index b22259e..f8b6c76 100644 --- a/gdriver-client/src/main.rs +++ b/gdriver-client/src/main.rs @@ -4,6 +4,7 @@ use tokio::sync::mpsc::{channel, Sender}; use crate::filesystem::{Filesystem, ShutdownRequest}; use gdriver_common::{ipc::sample::*, prelude::*}; +use tarpc::context::Context; use tarpc::{client, tokio_serde::formats::Json}; use tokio::task::JoinHandle; @@ -15,10 +16,11 @@ async fn main() -> Result<()> { // service::start().await?; let mount_options = &[MountOption::RW]; let (tx, rx) = channel(1); - let f = Filesystem::new( - service::create_client(CONFIGURATION.ip, CONFIGURATION.port).await?, - rx, - ); + let gdriver_client = service::create_client(CONFIGURATION.ip, CONFIGURATION.port).await?; + gdriver_client + .set_offline_mode(Context::current(), true) //TODO make this configurable + .await??; + let f = Filesystem::new(gdriver_client, rx); mount(f, &"/var/tmp/gdriver2_mount", mount_options, tx) .await? .await?; diff --git a/gdriver-common/src/ipc/gdriver_service.rs b/gdriver-common/src/ipc/gdriver_service.rs index a118165..f1dd5fa 100644 --- a/gdriver-common/src/ipc/gdriver_service.rs +++ b/gdriver-common/src/ipc/gdriver_service.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; #[tarpc::service] pub trait GDriverService { + async fn set_offline_mode(offline_mode: bool) -> StdResult<(), GDriverServiceError>; async fn get_file_by_name( name: OsString, parent: DriveId, @@ -52,6 +53,7 @@ lazy_static! { pub mod errors { use super::*; + use crate::path_resolve_error::PathResolveError; #[derive(Debug, Serialize, Deserialize, thiserror::Error)] pub enum GDriverServiceError { #[error("Error getting the settings: {0}")] @@ -102,6 +104,8 @@ pub mod errors { NotFound, #[error("Could not update drive info")] Update(String), + #[error("Could not resolve path")] + PathResolve(#[from] PathResolveError), } #[derive(Debug, Serialize, Deserialize, thiserror::Error)] diff --git a/gdriver-common/src/lib.rs b/gdriver-common/src/lib.rs index 74c758a..39b52c0 100644 --- a/gdriver-common/src/lib.rs +++ b/gdriver-common/src/lib.rs @@ -5,5 +5,6 @@ pub mod prelude; pub mod config; pub mod drive_structure; pub mod ipc; +pub mod path_resolve_error; pub mod project_dirs; pub mod tracing_setup; diff --git a/gdriver-common/src/path_resolve_error.rs b/gdriver-common/src/path_resolve_error.rs new file mode 100644 index 0000000..d6b08e1 --- /dev/null +++ b/gdriver-common/src/path_resolve_error.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, thiserror::Error)] +pub enum PathResolveError { + #[error("The path provided was invalid")] + InvalidPath, + #[error("Some other error occurred")] + Other(String), +}