Skip to content

Commit

Permalink
Merge pull request #22 from UCSD-E4E/feature/check-remote-on-upload
Browse files Browse the repository at this point in the history
Feature/check remote on upload
  • Loading branch information
ccrutchf authored Dec 10, 2024
2 parents 7205236 + f5e567d commit c0905d3
Show file tree
Hide file tree
Showing 3 changed files with 381 additions and 17 deletions.
92 changes: 81 additions & 11 deletions src/subcommands/main_subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ impl CustomTransferAgent for MainSubcommand {
}?;

self.file_station = Some(file_station);

let path = configuration.path.as_str();
match self.create_folder(path).await {

match self.create_target_folder().await {
Ok(_) => Ok(()),
Err(error) => {
error_init(1, error.to_string().as_str())?;
Expand Down Expand Up @@ -117,6 +116,18 @@ impl CustomTransferAgent for MainSubcommand {
};

let source_path = Path::new(source_path.as_str());
let target_path = format!(
"{}/{}",
configuration.path,
event.oid.clone().context("OID should not be none.")?
);

if self.exists_on_remote(target_path.as_str()).await? {
info!("Object already exists on server.");

return Ok(())
}

let file_station = self.file_station.clone().context("File Station should not be null")?;
file_station.upload(source_path, event.size.context("Size should not be null")?, configuration.path.as_str(), false, false, None, None, None, Some(progress_reporter)).await?;

Expand Down Expand Up @@ -144,21 +155,80 @@ impl MainSubcommand {
}

#[tracing::instrument]
async fn create_folder(&self, path: &str) -> Result<()> {
fn is_path_root(&self, path: &str) -> bool {
path == "/" || path == ""
}

#[tracing::instrument]
fn get_parent_path(&self, path: &str) -> Result<Option<String>> {
if self.is_path_root(path) {
return Ok(None)
}

let path_parts = path.split('/');
let name = path_parts.last().context("Our path should have a name since it's not the root.")?;
// We remove one extra character so that we don't have a trailing '/'.
Ok(Some(path[..(path.len() - name.len() - 1)].to_string()))
}

#[tracing::instrument]
fn get_name(&self, path: &str) -> Result<String> {
if self.is_path_root(path) {
return Ok("".to_string()); // We are the root. We don't have a name.
}

let path_parts = path.split('/');
let name = path_parts.last().context("Our path should have a name since it's not the root.")?;

Ok(name.to_string())
}

#[tracing::instrument]
async fn exists_on_remote(&self, path: &str) -> Result<bool> {
if self.is_path_root(path) {
info!("Path is root.");

return Ok(true); // The root should always exist. Don't need to ask the server to confirm.
}

let name = self.get_name(path)?;
let parent = self.get_parent_path(path)?.context("Path should not be root since we checked earlier.")?;

let file_station = self.file_station.clone().context("File Station should not be null")?;

if self.is_path_root(&parent) {
let shares = file_station.list_share(
None, None, None, None, None,
false, false, false, false, false, false, false).await?;

return Ok(shares.shares.iter().any(|share| share.name == name));
}
else {
let files = file_station.list(
&parent, None, None, None, None, None, None, None,
false, false, false, false, false, false, false).await?;

return Ok(files.files.iter().any(|file| file.name == name));
}
}

#[tracing::instrument]
async fn create_target_folder(&self) -> Result<()> {
let configuration = Configuration::load()?;

if self.exists_on_remote(&configuration.path).await? {
return Ok(()); // Exit early, handle trying to create a folder over a share.
}

// This is a System wide, cross-process lock.
let lock = NamedLock::create("git-lfs-synology::MainSubcommand::create_folder")?;
let lock = NamedLock::create("git-lfs-synology::MainSubcommand::create_target_folder")?;
let _guard = lock.lock()?;

let file_station = self.file_station.clone().context("File Station should not be null.")?;

let path_parts = configuration.path.split('/');
let name = path_parts.last().context("Our path should have a name")?;
// We remove one extra character so that we don't have a trailing '/'.
let folder_path_string = configuration.path[..(configuration.path.len() - name.len() - 1)].to_string();
let folder_path = folder_path_string.as_str();
let _folders = file_station.create_folder(folder_path, name, true).await?;
let name = self.get_name(&configuration.path)?;
let folder_path = self.get_parent_path(&configuration.path)?.context("Path should not be root.")?;
let _folders = file_station.create_folder(folder_path.as_str(), name.as_str(), true).await?;

Ok(())
}
Expand Down
176 changes: 170 additions & 6 deletions src/synology_api/file_station.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use urlencoding::encode;

use crate::credential_manager::Credential;

use super::{responses::{CreateFolderResponse, LoginError, LoginResponse, SynologyError, SynologyErrorStatus, SynologyResult, SynologyStatusCode}, ProgressReporter};
use super::{responses::{CreateFolderResponse, ListResponse, ListShareResponse, LoginError, LoginResponse, SynologyError, SynologyErrorStatus, SynologyResult, SynologyStatusCode}, ProgressReporter};

#[derive(Clone, Debug)]
pub struct SynologyFileStation {
Expand All @@ -27,7 +27,7 @@ impl SynologyFileStation {
}

#[tracing::instrument]
async fn get<T: DeserializeOwned>(&self, api: &str, method: &str, version: u32, parameters: &HashMap<&str, &str>) -> Result<T, SynologyErrorStatus> {
async fn get<T: DeserializeOwned>(&self, api: &str, method: &str, version: u32, parameters: &HashMap<&str, String>) -> Result<T, SynologyErrorStatus> {
match &self.sid {
Some(sid) => {
info!("Found sid, continuing.");
Expand Down Expand Up @@ -130,10 +130,10 @@ impl SynologyFileStation {
pub async fn create_folder(&self, folder_path: &str, name: &str, force_parent: bool) -> Result<CreateFolderResponse, SynologyErrorStatus> {
let force_parent_string = force_parent.to_string();

let mut parameters = HashMap::<&str, &str>::new();
parameters.insert("folder_path", folder_path);
parameters.insert("name", name);
parameters.insert("force_parent", force_parent_string.as_str());
let mut parameters = HashMap::<&str, String>::new();
parameters.insert("folder_path", folder_path.to_string());
parameters.insert("name", name.to_string());
parameters.insert("force_parent", force_parent_string);

self.get("SYNO.FileStation.CreateFolder", "create", 2, &parameters).await
}
Expand Down Expand Up @@ -206,6 +206,170 @@ impl SynologyFileStation {
}
}

#[allow(clippy::too_many_arguments)] // Allow this so that we better match the Synology API.
#[tracing::instrument]
pub async fn list(
&self,
folder_path: &str,
offset: Option<u64>,
limit: Option<u64>,
sort_by: Option<String>,
sort_direction: Option<String>,
pattern: Option<String>,
file_type: Option<String>,
goto_path: Option<String>,
include_real_path: bool,
include_size: bool,
include_owner: bool,
include_time: bool,
include_perm: bool,
include_mount_point_type: bool,
include_type: bool
) -> Result<ListResponse, SynologyErrorStatus> {
let mut parameters = HashMap::<&str, String>::new();
parameters.insert("folder_path", folder_path.to_string());

if let Some(offset) = offset {
parameters.insert("offset", offset.to_string());
}

if let Some(limit) = limit {
parameters.insert("limit", limit.to_string());
}

if let Some(sort_by) = sort_by {
parameters.insert("sort_by", sort_by);
}

if let Some(sort_direction) = sort_direction {
parameters.insert("sort_direction", sort_direction);
}

if let Some(pattern) = pattern {
parameters.insert("pattern", pattern);
}

if let Some(file_type) = file_type {
parameters.insert("filetype", file_type);
}

if let Some(goto_path) = goto_path {
parameters.insert("goto_path", goto_path);
}

let mut additional: String = String::new();

if include_real_path {
additional = format!("{},real_path", additional);
}

if include_size {
additional = format!("{},size", additional);
}

if include_owner {
additional = format!("{},owner", additional);
}

if include_time {
additional = format!("{},time", additional);
}

if include_perm {
additional = format!("{},perm", additional);
}

if include_mount_point_type {
additional = format!("{},mount_point_type", additional);
}

if include_type {
additional = format!("{},type", additional);
}

if additional.len() > 0 {
parameters.insert("additional", additional[1..].to_string());
}

self.get("SYNO.FileStation.List", "list", 2, &parameters).await
}

#[allow(clippy::too_many_arguments)] // Allow this so that we better match the Synology API.
#[tracing::instrument]
pub async fn list_share(
&self,
offset: Option<u64>,
limit: Option<u64>,
sort_by: Option<String>,
sort_direction: Option<String>,
only_writable: Option<bool>,
include_real_path: bool,
include_size: bool,
include_owner: bool,
include_time: bool,
include_perm: bool,
include_mount_point_type: bool,
include_volume_status: bool
) -> Result<ListShareResponse, SynologyErrorStatus> {
let mut parameters = HashMap::<&str, String>::new();

if let Some(offset) = offset {
parameters.insert("offset", offset.to_string());
}

if let Some(limit) = limit {
parameters.insert("limit", limit.to_string());
}

if let Some(sort_by) = sort_by {
parameters.insert("sort_by", sort_by);
}

if let Some(sort_direction) = sort_direction {
parameters.insert("sort_direction", sort_direction);
}

if let Some(only_writable) = only_writable {
parameters.insert("only_writable", only_writable.to_string());
}

let mut additional: String = String::new();

if include_real_path {
additional = format!("{},real_path", additional);
}

if include_size {
additional = format!("{},size", additional);
}

if include_owner {
additional = format!("{},owner", additional);
}

if include_time {
additional = format!("{},time", additional);
}

if include_perm {
additional = format!("{},perm", additional);
}

if include_mount_point_type {
additional = format!("{},mount_point_type", additional);
}

if include_volume_status {
additional = format!("{},volume_status", additional);
}

if additional.len() > 0 {
parameters.insert("additional", additional[1..].to_string());
}

self.get("SYNO.FileStation.List", "list_share", 2, &parameters).await
}

#[tracing::instrument]
pub async fn login(&mut self, credential: &Credential, enable_device_token: bool, totp: Option<String>) -> Result<Credential, SynologyErrorStatus> {
let device_name = format!(
Expand Down
Loading

0 comments on commit c0905d3

Please sign in to comment.