]> git.r.bdr.sh - rbdr/blog/commitdiff
Add part of the implementation for add
authorRuben Beltran del Rio <redacted>
Sun, 25 Feb 2024 22:06:19 +0000 (22:06 +0000)
committerRuben Beltran del Rio <redacted>
Sun, 25 Feb 2024 22:06:19 +0000 (22:06 +0000)
17 files changed:
src/command/add.rs
src/command/add_remote.rs
src/command/generate.rs
src/command/help.rs
src/command/mod.rs
src/command/publish.rs
src/command/publish_archive.rs
src/command/remove_remote.rs
src/command/status/configuration_status.rs
src/command/status/mod.rs
src/command/sync_down.rs
src/command/sync_up.rs
src/command/update.rs
src/command/version.rs
src/constants.rs [new file with mode: 0644]
src/main.rs
src/metadata.rs [new file with mode: 0644]

index bc1bea0bb06c86f2077b374b7de68a51cbd0cb5b..3e0e9876cbb5268aacace5762a202ea74de38a98 100644 (file)
@@ -1,10 +1,12 @@
-use std::io::Result;
+use std::fs::{create_dir_all, remove_dir_all, rename};
+use std::io::{Result, Error};
 use super::{
     generate::Generate,
     sync_down::SyncDown,
     sync_up::SyncUp,
     update::Update
 };
+use crate::configuration::Configuration;
 
 pub struct Add;
 
@@ -12,6 +14,24 @@ impl Add {
     pub fn new() -> Self {
         Add
     }
+
+    // moves posts to their next
+    fn shift(&self, configuration: &Configuration) -> Result<()> {
+        for i in (0..configuration.max_posts).rev() {
+            let source = configuration.posts_directory.join(i.to_string());
+            let target = configuration.posts_directory.join((i + 1).to_string());
+
+            println!("Moving {} source to {}", source.display(), target.display());
+
+            if source.exists() {
+                match rename(&source, &target) {
+                    Ok(_) => continue,
+                    Err(e) => return Err(Error::new(e.kind(), format!("Could not shift post {} to {}", source.display(), target.display())))
+                }
+            }
+        }
+        Ok(())
+    }
 }
 
 impl super::Command for Add {
@@ -19,9 +39,23 @@ impl super::Command for Add {
         vec![Box::new(SyncDown::new())]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
-        println!("Add: {:?}!", input);
-        return Ok(())
+    fn execute(&self, _: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> {
+        match create_dir_all(&configuration.posts_directory) {
+            Ok(_) => {
+                match self.shift(configuration) {
+                    Ok(_) => {
+                        let first_directory = configuration.posts_directory.join("0");
+                        let _ = remove_dir_all(&first_directory);
+                        match create_dir_all(&configuration.posts_directory) {
+                            Ok(_) => Ok(()),
+                            Err(e) => Err(Error::new(e.kind(), format!("Could not create first post directory")))
+                        }
+                    },
+                    Err(e) => Err(e)
+                }
+            },
+            Err(e) => Err(Error::new(e.kind(), format!("Could not create posts directory")))
+        }
     }
 
     fn after_dependencies(&self) -> Vec<Box<dyn super::Command>> {
index 3d901038e9fe53eab703595f35353d930acde88c..c98330fbf7d94fdeae7c64abb841b56c1d5f4c13 100644 (file)
@@ -1,4 +1,5 @@
 use std::io::Result;
+use crate::configuration::Configuration;
 
 pub struct AddRemote;
 
@@ -13,7 +14,7 @@ impl super::Command for AddRemote {
         vec![]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
+    fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
         println!("Add Remote: {:?}!", input);
         return Ok(())
     }
index 59babd553080ea66e9eae2dc662364f30456d401..fac73ff37de30f57234eadde8118755e2fc8031c 100644 (file)
@@ -1,4 +1,5 @@
 use std::io::Result;
+use crate::configuration::Configuration;
 
 pub struct Generate;
 
@@ -13,7 +14,7 @@ impl super::Command for Generate {
         vec![]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
+    fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
         println!("GENERATE! {:?}", input);
         return Ok(())
     }
index 7ddcba8f5b9d6b275b1bdf0a8f625380d3f0d65f..8d2a2c0adb7ee111d0280056e83448a5d4991224 100644 (file)
@@ -1,5 +1,6 @@
 use std::io::Result;
 use super::available_commands;
+use crate::configuration::Configuration;
 
 pub struct Help;
 
@@ -14,7 +15,7 @@ impl super::Command for Help {
         vec![]
     }
 
-    fn execute(&self, _: Option<&String>) -> Result<()> {
+    fn execute(&self, _: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
         let commands = available_commands();
         println!("Usage:");
         println!("");
index 211b10f6dbf757da70a02eb127597ec90738b2d5..0c96e6f57d7d51af6811dc0bbed4edb2c6c87a57 100644 (file)
@@ -27,9 +27,11 @@ use version::Version;
 use status::Status;
 use help::Help;
 
+use crate::configuration::Configuration;
+
 pub trait Command {
     fn before_dependencies(&self) -> Vec<Box<dyn Command>>;
-    fn execute(&self, input: Option<&String>) -> Result<()>;
+    fn execute(&self, input: Option<&String>, configuration: &Configuration, command: &String) -> Result<()>;
     fn after_dependencies(&self) -> Vec<Box<dyn Command>>;
     fn command(&self) -> &'static str;
     fn help(&self) -> &'static str;
index d6116a4d63ba1868b2cab3e8829b54e741686953..881c9876e8d6c9b590340a7bef1dc100aa179bbe 100644 (file)
@@ -1,4 +1,5 @@
 use std::io::Result;
+use crate::configuration::Configuration;
 
 pub struct Publish;
 
@@ -13,7 +14,7 @@ impl super::Command for Publish {
         vec![]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
+    fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
         println!("Publish: {:?}!", input);
         return Ok(())
     }
index 6b98b1a4a867645c21650735a2136e0955b0dcbc..f5f114aec14dfd949ec57d1e7b81ec247f2bab27 100644 (file)
@@ -1,4 +1,5 @@
 use std::io::Result;
+use crate::configuration::Configuration;
 
 pub struct PublishArchive;
 
@@ -13,7 +14,7 @@ impl super::Command for PublishArchive {
         vec![]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
+    fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
         println!("Publish Archive: {:?}!", input);
         return Ok(())
     }
index cfc5cdb848b7b154389196fa31fa379844facd9d..8188beb5c7991391c85cea7a4cf3430ac234938c 100644 (file)
@@ -1,4 +1,5 @@
 use std::io::Result;
+use crate::configuration::Configuration;
 
 pub struct RemoveRemote;
 
@@ -13,7 +14,7 @@ impl super::Command for RemoveRemote {
         vec![]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
+    fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
         println!("Remove Remote: {:?}!", input);
         return Ok(())
     }
index 67bf277dcc9d4a38c43d6122a52a45926b0f794c..6d4e56a817c008ebf838ea47bb08915ee15b2f1b 100644 (file)
@@ -2,36 +2,35 @@ use std::fs;
 use std::path::PathBuf;
 use crate::configuration::Configuration;
 
-pub fn status() -> String {
-    let configuration = Configuration::new();
+pub fn status(configuration: &Configuration) -> String {
     let mut status_message = String::new();
 
     status_message.push_str("# Configuration\n");
-    status_message.push_str("## Directories\n");
+    status_message.push_str("\n## Directories\n");
 
     // Main Configuration Locations
-    status_message.push_str(&get_directory_stats("Configuration", configuration.config_directory));
-    status_message.push_str(&get_directory_stats("Data", configuration.data_directory));
-    status_message.push_str(&get_directory_stats("Output", configuration.output_directory));
+    status_message.push_str(&get_directory_stats("Configuration", &configuration.config_directory));
+    status_message.push_str(&get_directory_stats("Data", &configuration.data_directory));
+    status_message.push_str(&get_directory_stats("Output", &configuration.output_directory));
 
-    status_message.push_str("## Blog Settings\n");
+    status_message.push_str("\n## Blog Settings\n");
     status_message.push_str(&format!("Number of posts to keep: {}\n", configuration.max_posts));
     status_message
 }
 
-fn get_directory_stats(label: &str, directory: PathBuf) -> String {
+fn get_directory_stats(label: &str, directory: &PathBuf) -> String {
     let mut status_message = String::new();
 
-    status_message.push_str(&format!("{}: {}\n", label, directory.display()));
+    status_message.push_str(&format!("{}: {}", label, directory.display()));
     if directory.exists() {
-        status_message.push_str(&format!("{} directory exists.\n", label));
+        status_message.push_str("Exists ");
         if fs::read_dir(&directory).is_ok() {
-            status_message.push_str(&format!("{} directory is readable.\n", label));
+            status_message.push_str("and is readable.\n");
         } else {
-            status_message.push_str(&format!("{} directory is not readable.\n", label));
+            status_message.push_str("but is not readable.\n");
         }
     } else {
-        status_message.push_str(&format!("{} directory does not exist.\n", label));
+        status_message.push_str("Does not exist.\n");
     }
 
     status_message
index ba73184733ac159b7fd008b8f6a47545d0516d2b..e8754f59eadb5e334c08c5f39c61fda299043efc 100644 (file)
@@ -1,6 +1,7 @@
 mod configuration_status;
 
 use std::io::Result;
+use crate::configuration::Configuration;
 
 pub struct Status;
 
@@ -15,10 +16,10 @@ impl super::Command for Status {
         vec![]
     }
 
-    fn execute(&self, _: Option<&String>) -> Result<()> {
+    fn execute(&self, _: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> {
         let status_providers = available_status_providers();
         for status_provider in status_providers {
-            println!("{}", status_provider());
+            println!("{}", status_provider(configuration));
         }
         return Ok(())
     }
@@ -36,7 +37,7 @@ impl super::Command for Status {
     }
 }
 
-fn available_status_providers() -> Vec<fn() -> String> {
+fn available_status_providers() -> Vec<fn(&Configuration) -> String> {
     vec![
         configuration_status::status,
     ]
index c00b01a90aecdfa6822e21a063789aba81120511..dc4114e8d31f0836a047b5fbc76c1bb6127e422c 100644 (file)
@@ -1,4 +1,6 @@
-use std::io::Result;
+use std::fs::create_dir_all;
+use std::io::{Result, Error};
+use crate::configuration::Configuration;
 
 pub struct SyncDown;
 
@@ -13,9 +15,17 @@ impl super::Command for SyncDown {
         vec![]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
-        println!("Sync Down: {:?}!", input);
-        return Ok(())
+    fn execute(&self, _: Option<&String>, configuration: &Configuration, command: &String) -> Result<()> {
+        match create_dir_all(&configuration.data_directory) {
+            Ok(_) => {
+                // We only care to show these warnings if this is the primary command.
+                if command == self.command() {
+                    println!("WARNING: Sync Down Not yet implemented");
+                }
+                return Ok(())
+            },
+            Err(e) => Err(Error::new(e.kind(), format!("Could not create data directory")))
+        }
     }
 
     fn after_dependencies(&self) -> Vec<Box<dyn super::Command>> {
index d35ce7ffd2d41a20c4ae339f6635843f8c4bf53b..635de100a4c8339dc633704130a136e651697da9 100644 (file)
@@ -1,4 +1,5 @@
 use std::io::Result;
+use crate::configuration::Configuration;
 
 pub struct SyncUp;
 
@@ -13,7 +14,7 @@ impl super::Command for SyncUp {
         vec![]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
+    fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
         println!("Sync Up: {:?}!", input);
         return Ok(())
     }
index 0a0211cbae485729691120850c88dca78c01a4f9..ed6b066556042f9325d60167d523a4fd49bcf81c 100644 (file)
@@ -1,5 +1,10 @@
-use std::io::Result;
+use std::fs::create_dir_all;
+use std::io::{Result, Error, ErrorKind};
+use std::path::PathBuf;
 use super::{sync_down::SyncDown, generate::Generate, sync_up::SyncUp};
+use crate::configuration::Configuration;
+use crate::constants::METADATA_FILENAME;
+use crate::metadata::Metadata;
 
 pub struct Update;
 
@@ -7,6 +12,18 @@ impl Update {
     pub fn new() -> Self {
         Update
     }
+
+    fn copy_post(&self, post_location: &PathBuf) {
+        
+    }
+
+    fn write_metadata(&self, metadata: Metadata, metadata_location: &PathBuf) {
+        
+    }
+
+    fn archive(&self, post_location: &PathBuf) {
+        
+    }
 }
 
 impl super::Command for Update {
@@ -14,8 +31,23 @@ impl super::Command for Update {
         vec![Box::new(SyncDown::new())]
     }
 
-    fn execute(&self, input: Option<&String>) -> Result<()> {
-        println!("Update: {:?}!", input);
+    fn execute(&self, input: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> {
+        let input = input.expect("You must provide a path to a post");
+        let post_location = PathBuf::from(input);
+        if !post_location.exists() {
+            return Err(Error::new(ErrorKind::NotFound, "The path provided does not exist"));
+        }
+
+        create_dir_all(&configuration.posts_directory)?;
+
+        let metadata_file_path = configuration.posts_directory
+            .join("0")
+            .join(METADATA_FILENAME);
+        let metadata = Metadata::read_or_create(&metadata_file_path);
+
+        self.copy_post(&post_location);
+        self.write_metadata(metadata, &metadata_file_path);
+        self.archive(&post_location);
         return Ok(())
     }
 
index a8b970b627eafdc74f6a7dc062305b3aa663250b..e566c5a4e3cfa6ece8edb957f10a4ca372684669 100644 (file)
@@ -1,4 +1,5 @@
 use std::io::Result;
+use crate::configuration::Configuration;
 
 pub struct Version;
 
@@ -13,7 +14,7 @@ impl super::Command for Version {
         vec![]
     }
 
-    fn execute(&self, _: Option<&String>) -> Result<()> {
+    fn execute(&self, _: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
         let version = env!("CARGO_PKG_VERSION");
         println!("{}", version);
         return Ok(())
diff --git a/src/constants.rs b/src/constants.rs
new file mode 100644 (file)
index 0000000..4519426
--- /dev/null
@@ -0,0 +1 @@
+pub const METADATA_FILENAME: &str = "metadata.json";
index bf1e1844a2162646f2046f728af45de84a187aef..4cb8036f7fb92f803f007f05e0ee3ccf091c0ab5 100644 (file)
@@ -1,13 +1,17 @@
 // mod argument_parser;
 mod configuration;
 mod command;
+mod constants;
+mod metadata;
 
 use std::iter::once;
 use std::env::args;
 use std::io::Result;
 use command::{available_commands, Command, help::Help};
+use configuration::Configuration;
 
 fn main() -> Result<()> {
+    let configuration = Configuration::new();
     let commands = available_commands();
     let arguments: Vec<String> = args().collect();
 
@@ -23,7 +27,7 @@ fn main() -> Result<()> {
                 .collect();
 
             for command in command_chain {
-                let result = command.execute(arguments.get(2));
+                let result = command.execute(arguments.get(2), &configuration, command_name);
                 if let Err(_) = result {
                     return result;
                 }
@@ -33,5 +37,5 @@ fn main() -> Result<()> {
         }
     }
 
-    Help::new().execute(None)
+    Help::new().execute(None, &configuration, &"help".to_string())
 }
diff --git a/src/metadata.rs b/src/metadata.rs
new file mode 100644 (file)
index 0000000..97ef0d3
--- /dev/null
@@ -0,0 +1,70 @@
+use std::fs::File;
+use std::path::PathBuf;
+use std::io::Read;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+pub struct Metadata {
+    pub id: String,
+    pub created_on: u128
+}
+
+impl Metadata {
+    pub fn serialize(&self) -> String {
+        format!(r#"{{\n  "id": "{}",\n  "created_on": {}\n}}"#, self.id, self.created_on)
+    }
+
+    pub fn deserialize(input: &str) -> Option<Metadata> {
+        let clean_input = input
+            .chars()
+            .filter(|c| !c.is_whitespace())
+            .collect::<String>();
+
+        let id = Metadata::read_field(&clean_input, "id")?;
+        let created_on = Metadata::read_field(&clean_input, "createdOn")
+            .or_else(|| Metadata::read_field(&clean_input, "created_on"))?;
+
+        Some(Metadata {
+            id,
+            created_on: created_on.parse().ok()?
+        })
+    }
+
+    pub fn read_or_create(file_path: &PathBuf) -> Metadata {
+        match Metadata::read_metadata_file(file_path) {
+            Some(metadata) => metadata,
+            None => {
+                let timestamp = SystemTime::now()
+                    .duration_since(UNIX_EPOCH)
+                    .map(|duration| duration.as_millis() as u128)
+                    .unwrap_or_else(|_| 0);
+                return Metadata {
+                    id: timestamp.to_string(),
+                    created_on: timestamp
+                }
+            }
+        }
+    }
+
+    fn read_metadata_file(file_path: &PathBuf) -> Option<Metadata> {
+        let mut file = File::open(file_path).ok()?;
+        let mut contents = String::new();
+        file.read_to_string(&mut contents).ok()?;
+        Metadata::deserialize(&contents)
+    }
+
+
+    fn read_field(input: &str, field_name: &str) -> Option<String> {
+        let key_pattern = format!(r#""{}":""#, field_name);
+        input.find(&key_pattern)
+            .and_then(|start| {
+                let value_start = start + key_pattern.len();
+                let value_end = input[value_start..]
+                    .find(|c: char| c == ',' || c == '}' || c == '\n')
+                    .unwrap_or_else(|| input[value_start..].len()) + value_start;
+
+                let raw_value = &input[value_start..value_end];
+                let value = raw_value.trim_matches('"').to_string();
+                Some(value)
+            })
+    }
+}