Skip to content

Commit

Permalink
feat: creates folder on synology when initializing.
Browse files Browse the repository at this point in the history
  • Loading branch information
ccrutchf committed Nov 28, 2024
1 parent 718ac26 commit c3f55c4
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 50 deletions.
9 changes: 1 addition & 8 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
"request": "launch",
"name": "Launch",
"program": "${workspaceFolder}/target/debug/git-lfs-synology",
"args": [
"login",
"--url",
"https://e4e-nas.ucsd.edu:6021",
"--user",
"<user>"
],
"cwd": "${workspaceFolder}"
"cwd": "${workspaceFolder}/../git-lfs-test"
}
]
}
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ educe = "0.6.0"
futures-macro = "0.3.31"
gix-config = "0.42.0"
keyring = { version = "3.6.1", features = ["apple-native", "windows-native", "sync-secret-service"] }
named-lock = "0.4.1"
num-derive = "0.4.2"
num-traits = "0.2.19"
reqwest = { version = "0.12.9", features = ["json"] }
Expand All @@ -26,3 +27,4 @@ tracing = "0.1.40"
tracing-appender = "0.2.3"
tracing-subscriber = "0.3.18"
url = "2.5.4"
urlencoding = "2.1.3"
8 changes: 5 additions & 3 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::{anyhow, Context, Ok, Result};
use gix_config::File;
use serde::{Deserialize, Serialize};
use tracing::debug;
use tracing::info;
use url::Url;

#[derive(Debug, Serialize, Deserialize, Clone)]
Expand All @@ -23,7 +23,7 @@ impl Configuration {
let section = config.section("lfs", None)?;

let url = section.value("url").context("Url should be set.")?.to_string();
debug!("Url found: {}", url);
info!("Url found: {}", url);

let url = if url.starts_with("filestation-secure://") {
Ok(url.replace("filestation-secure", "https"))
Expand All @@ -34,13 +34,15 @@ impl Configuration {
else {
Err(anyhow!("Url is not set incorrectly."))
}?;
debug!("Url updated: {}", url);
info!("Url updated: {}", url);

let url_parsed = Url::parse(url.as_str())?;

let path = url_parsed.path();
let nas_url = url.replace(path, "");

info!("nas_url: \"{}\", path: \"{}\".", nas_url, path);

Ok(
Configuration {
nas_url: nas_url.to_string(),
Expand Down
2 changes: 1 addition & 1 deletion src/credential_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl Credential {

#[tracing::instrument]
pub fn totp(&self) -> Option<String> {
match self.totp_command.clone() {
match &self.totp_command {
Some(totp_command) => {
info!("TOTP command found.");

Expand Down
44 changes: 29 additions & 15 deletions src/git_lfs/git_lfs_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::io;

use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use tracing::{debug, info};
use tracing::{info, warn};

use super::CustomTransferAgent;

Expand All @@ -15,15 +15,18 @@ pub fn error(code: u32, message: &str) -> Result<()> {
};

println!("{}", serde_json::to_string(&error_json)?);

Ok(())
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct EventJsonPartial {
struct EventJson {
event: String
}

pub struct Event {
event: EventType
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct ErrorJson {
error: ErrorJsonInner
Expand All @@ -35,10 +38,6 @@ struct ErrorJsonInner {
message: String
}

pub struct Event {
event: EventType
}

pub enum EventType {
// Complete,
Download,
Expand All @@ -62,7 +61,8 @@ impl<'custom_transfer_agent, T: CustomTransferAgent> GitLfsParser<'custom_transf
}

#[tracing::instrument]
fn parse(&self, event: &EventJsonPartial) -> Result<Event> {
fn parse(&self, event: &EventJson) -> Result<Event> {
info!("Event received: \"{}\".", event.event);
let event_type = match event.event.as_str() {
"download" => EventType::Download,
"init" => EventType::Init,
Expand All @@ -82,8 +82,9 @@ impl<'custom_transfer_agent, T: CustomTransferAgent> GitLfsParser<'custom_transf
let stdin = io::stdin();

stdin.read_line(&mut buffer)?;
debug!("Received JSON: \"{}\".", buffer);
let event = self.parse(&serde_json::from_str::<EventJsonPartial>(buffer.as_str())?)?;
info!("Received JSON: \"{}\".", buffer);
let event = self.parse(&serde_json::from_str::<EventJson>(buffer.as_str())?)?;
buffer.clear();

let init_result = match event.event {
EventType::Init => {
Expand All @@ -94,14 +95,23 @@ impl<'custom_transfer_agent, T: CustomTransferAgent> GitLfsParser<'custom_transf
};

match init_result {
Ok(_) => println!("{{ }}"), // success
Err(err) => error(1, err.to_string().as_str())? // an error occurred
Ok(_) => {
info!("Init event parsed correctly.");

println!("{{ }}")
}, // success
Err(err) => {
warn!("An error occurred \"{}\".", err);

error(1, err.to_string().as_str())? // an error occurred
}
}

loop {
stdin.read_line(&mut buffer)?;
debug!("Received JSON: \"{}\".", buffer);
let event = self.parse(&serde_json::from_str::<EventJsonPartial>(buffer.as_str())?)?;
info!("Received JSON: \"{}\".", buffer);
let event = self.parse(&serde_json::from_str::<EventJson>(buffer.as_str())?)?;
buffer.clear();

match event.event {
EventType::Download => {
Expand All @@ -118,7 +128,11 @@ impl<'custom_transfer_agent, T: CustomTransferAgent> GitLfsParser<'custom_transf

break;
},
_ => bail!("Event type not supported in context.")
_ => {
warn!("Event type not supported in context.");

bail!("Event type not supported in context.")
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Result;
use clap::{Command, Arg};
use clap::{Arg, Command};
use tracing::error;
use users_dirs::get_config_dir;
use tracing_appender::rolling;
Expand Down Expand Up @@ -79,7 +79,6 @@ fn cli() -> Command {
#[tokio::main]
async fn main() -> Result<()> {
setup_logging()?;

let matches = cli().get_matches();

let result: Result<()> = match matches.subcommand() {
Expand Down
28 changes: 27 additions & 1 deletion src/subcommands/main_subcommand.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::{Context, Result};
use clap::ArgMatches;
use named_lock::NamedLock;

use crate::{configuration::Configuration, credential_manager::CredentialManager, git_lfs::{CustomTransferAgent, Event, GitLfsParser}, synology_api::SynologyFileStation};

Expand All @@ -12,7 +13,7 @@ pub struct MainSubcommand {

impl CustomTransferAgent for MainSubcommand {
#[tracing::instrument]
async fn download(&mut self, _: &Event) -> Result<()> {
async fn download(&mut self, _: &Event) -> Result<()> {
Ok(())
}

Expand All @@ -29,6 +30,9 @@ impl CustomTransferAgent for MainSubcommand {

self.file_station = Some(file_station);

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

Ok(())
}

Expand Down Expand Up @@ -62,4 +66,26 @@ impl MainSubcommand {
file_station: None
}
}

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

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

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

// Check if folder exists

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, false).await?;

Ok(())
}
}
73 changes: 58 additions & 15 deletions src/synology_api/file_station.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,63 @@
use std::collections::HashMap;

use num_traits::FromPrimitive;
use reqwest::{Error, Response, StatusCode};
use serde::de::DeserializeOwned;
use tracing::{info, warn};
use urlencoding::encode;

use crate::credential_manager::Credential;

use super::responses::{LoginError, LoginResult, SynologyEmptyError, SynologyError, SynologyErrorStatus, SynologyResult, SynologyStatusCode};
use super::responses::{CreateFolderResult, LoginError, LoginResult, SynologyError, SynologyErrorStatus, SynologyResult, SynologyStatusCode};

#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct SynologyFileStation {
sid: Option<String>,
url: String,
version: u8
url: String
}

impl SynologyFileStation {
#[tracing::instrument]
pub fn new(url: &str) -> SynologyFileStation {
SynologyFileStation::new_with_version(url, 7)
SynologyFileStation {
sid: None,
url: url.to_string()
}
}

#[tracing::instrument]
pub fn new_with_version(url: &str, version: u8) -> SynologyFileStation {
SynologyFileStation {
sid: None,
url: url.to_string(),
version
async fn get<T: DeserializeOwned>(&self, api: &str, method: &str, version: u32, parameters: &HashMap<&str, &str>) -> Result<T, SynologyErrorStatus> {
match &self.sid {
Some(sid) => {
info!("Found sid, continuing.");
let mut url = format!(
"{}/webapi/entry.cgi?api={}&version={}&method={}&_sid={}",
self.url,
api,
version,
method,
sid.as_str()
);

for (key, value) in parameters {
url = format!(
"{}&{}={}",
url,
key,
encode(value)
);
}

info!("Get: \"{}\".", url);

let response = reqwest::get(url).await;
Ok(self.parse(response).await?)
},
None => {
info!("No sid found. Not logged in");

Err(SynologyErrorStatus::NotLoggedIn)
}
}
}

Expand All @@ -37,7 +69,7 @@ impl SynologyFileStation {
StatusCode::OK => {
match response.text().await {
Ok(text) => {
info!("Parsing response from server.");
info!("Parsing response from server. Response was \"{}\".", text);
let result = serde_json::from_str::<SynologyResult<TData, TError>>(text.as_str());

match result {
Expand All @@ -61,7 +93,7 @@ impl SynologyFileStation {

#[tracing::instrument]
async fn parse<T: DeserializeOwned>(&self, response: Result<Response, Error>) -> Result<T, SynologyErrorStatus> {
let (data, error) = self.parse_data_and_error::<T, SynologyEmptyError>(response).await?;
let (data, error) = self.parse_data_and_error::<T, Vec<HashMap<String, String>>>(response).await?;

match error {
Some(error) => {
Expand All @@ -85,14 +117,25 @@ impl SynologyFileStation {
}
}

pub async fn create_folder(&self, folder_path: &str, name: &str, force_parent: bool) -> Result<CreateFolderResult, 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());

Ok(self.get("SYNO.FileStation.CreateFolder", "create", 2, &parameters).await?)
}

#[tracing::instrument]
pub async fn login(&mut self, credential: &Credential) -> Result<(), SynologyErrorStatus> {
let login_url = format!(
"{}/webapi/entry.cgi?api=SYNO.API.Auth&version={}&method=login&account={}&passwd={}&session=FileStation&fromat=sid",
self.url,
self.version,
6,
credential.user,
credential.password
encode(credential.password.as_str()) // Encode the password in case it has characters not allowed in URLs in it.
);

// Make initial request to the server. This will fail if the user needs a TOTP.
Expand Down Expand Up @@ -120,7 +163,7 @@ impl SynologyFileStation {
let login_url = format!(
"{}/webapi/entry.cgi?api=SYNO.API.Auth&version={}&method=login&account={}&passwd={}&session=FileStation&fromat=sid&otp_code={}",
self.url,
self.version,
6,
credential.user,
errors.token,
totp
Expand Down
Loading

0 comments on commit c3f55c4

Please sign in to comment.