Browse Source

Show metadata properties with info CLI command, check if file exists

timvisee 7 years ago
parent
commit
a7827197a7

+ 0 - 2
IDEAS.md

@@ -1,7 +1,5 @@
 # Ideas
 - Endpoints:
-  - exists
-  - metadata
   - delete
 - allow creating non existent directories with the `-f` flag 
 - only allow file extension renaming on upload with `-f` flag

+ 36 - 0
api/src/action/download.rs

@@ -18,6 +18,10 @@ use crypto::sig::signature_encoded;
 use ext::status_code::StatusCodeExt;
 use file::remote_file::RemoteFile;
 use reader::{EncryptedFileWriter, ProgressReporter, ProgressWriter};
+use super::exists::{
+    Error as ExistsError,
+    Exists as ExistsAction,
+};
 use super::metadata::{
     Error as MetadataError,
     Metadata as MetadataAction,
@@ -55,6 +59,27 @@ impl<'a> Download<'a> {
         client: &Client,
         reporter: Arc<Mutex<ProgressReporter>>,
     ) -> Result<(), Error> {
+        // Make sure the given file exists
+        let exist_response = ExistsAction::new(&self.file)
+            .invoke(&client)?;
+
+        // Return an error if the file does not exist
+        if !exist_response.exists() {
+            return Err(Error::Expired);
+        }
+
+        // Make sure a password is given when it is required
+        let has_password = self.password.is_some();
+        if has_password != exist_response.has_password() {
+            if has_password {
+                // TODO: show a proper message here
+                println!("file not password protected, ignoring password");
+            } else {
+                // TODO: show a propper error here, or prompt for the password
+                panic!("password required");
+            }
+        }
+
         // Create a key set for the file
         let mut key = KeySet::from(self.file, self.password.as_ref());
 
@@ -232,6 +257,11 @@ impl<'a> Download<'a> {
 
 #[derive(Fail, Debug)]
 pub enum Error {
+    /// An error occurred while checking whether the file exists on the
+    /// server.
+    #[fail(display = "Failed to check whether the file exists")]
+    Exists(#[cause] ExistsError),
+
     /// An error occurred while fetching the metadata of the file.
     /// This step is required in order to succsessfully decrypt the
     /// file that will be downloaded.
@@ -257,6 +287,12 @@ pub enum Error {
     File(String, #[cause] FileError),
 }
 
+impl From<ExistsError> for Error {
+    fn from(err: ExistsError) -> Error {
+        Error::Exists(err)
+    }
+}
+
 impl From<MetadataError> for Error {
     fn from(err: MetadataError) -> Error {
         Error::Meta(err)

+ 7 - 1
api/src/action/exists.rs

@@ -49,8 +49,9 @@ impl<'a> Exists<'a> {
         }
 
         // Parse the response
-        let response = response.json::<ExistsResponse>()
+        let mut response = response.json::<ExistsResponse>()
             .map_err(|_| Error::Malformed)?;
+        response.set_exists(true);
 
         // TODO: fetch the metadata nonce from the response headers
 
@@ -84,6 +85,11 @@ impl ExistsResponse {
         self.exists
     }
 
+    /// Set whether the remote file exists.
+    pub fn set_exists(&mut self, exists: bool) {
+        self.exists = exists;
+    }
+
     /// Whether the remote file is protected by a password.
     pub fn has_password(&self) -> bool {
         self.has_password

+ 5 - 0
api/src/file/metadata.rs

@@ -52,6 +52,11 @@ impl Metadata {
         &self.name
     }
 
+    /// Get the file MIME type.
+    pub fn mime(&self) -> &str {
+        &self.mime
+    }
+
     /// Get the input vector
     // TODO: use an input vector length from a constant
     pub fn iv(&self) -> [u8; 12] {

+ 5 - 0
api/src/file/remote_file.rs

@@ -133,6 +133,11 @@ impl RemoteFile {
         ))
     }
 
+    /// Get the file ID.
+    pub fn id(&self) -> &str {
+        &self.id
+    }
+
     /// Get the raw secret.
     pub fn secret_raw(&self) -> &Vec<u8> {
         // A secret must have been set

+ 81 - 6
cli/src/action/info.rs

@@ -1,9 +1,21 @@
-use ffsend_api::action::info::Info as ApiInfo;
-use ffsend_api::file::remote_file::RemoteFile;
+use failure::Fail;
+use ffsend_api::action::exists::{
+    Error as ExistsError,
+    Exists as ApiExists,
+};
+use ffsend_api::action::info::{
+    Error as InfoError,
+    Info as ApiInfo,
+};
+use ffsend_api::action::metadata::Metadata as ApiMetadata;
+use ffsend_api::file::remote_file::{
+    FileParseError,
+    RemoteFile,
+};
 use ffsend_api::reqwest::Client;
 
 use cmd::cmd_info::CmdInfo;
-use error::ActionError;
+use util::print_error;
 
 /// A file info action.
 pub struct Info<'a> {
@@ -20,7 +32,7 @@ impl<'a> Info<'a> {
 
     /// Invoke the info action.
     // TODO: create a trait for this method
-    pub fn invoke(&self) -> Result<(), ActionError> {
+    pub fn invoke(&self) -> Result<(), Error> {
         // Get the share URL
         let url = self.cmd.url();
 
@@ -33,13 +45,76 @@ impl<'a> Info<'a> {
 
         // TODO: show an informative error if the owner token isn't set
 
-        // Execute the info fetch action
+        // Make sure the file exists
+        let exists_response = ApiExists::new(&file)
+            .invoke(&client)?;
+
+        // Make sure the file exists
+        if !exists_response.exists() {
+            return Err(Error::Expired);
+        }
+
+        // TODO: make sure a password is set if required
+
+        // Fetch both file info and metadata
         let info = ApiInfo::new(&file, None).invoke(&client)?;
+        // TODO: supply a password here
+        let metadata = ApiMetadata::new(&file, None).invoke(&client)
+            .map_err(|err| print_error(err.context(
+                "Failed to fetch file metadata, showing limited info",
+            )))
+            .ok();
 
         // Print the result
+        println!("ID: {}", file.id());
+        if let Some(metadata) = metadata {
+            println!("File name: {}", metadata.metadata().name());
+            println!("MIME type: {}", metadata.metadata().mime());
+        }
         println!("Downloads: {} of {}", info.download_count(), info.download_limit());
-        println!("TTL: {}", info.ttl_millis());
+        println!("TTL: {} ms", info.ttl_millis());
+
+        // TODO: show the file size, fetch TTL from metadata?
 
         Ok(())
     }
 }
+
+#[derive(Debug, Fail)]
+pub enum Error {
+    /// Failed to parse a share URL, it was invalid.
+    /// This error is not related to a specific action.
+    #[fail(display = "Invalid share URL")]
+    InvalidUrl(#[cause] FileParseError),
+
+    /// An error occurred while checking if the file exists.
+    #[fail(display = "Failed to check whether the file exists")]
+    Exists(#[cause] ExistsError),
+
+    /// An error occurred while fetching the file information.
+    #[fail(display = "Failed to fetch file info")]
+    Info(#[cause] InfoError),
+
+    /// The given Send file has expired, or did never exist in the first place.
+    // TODO: do not return an error, but write to stdout that the file does not exist
+    #[fail(display = "The file has expired or did never exist")]
+    Expired,
+}
+
+impl From<FileParseError> for Error {
+    fn from(err: FileParseError) -> Error {
+        Error::InvalidUrl(err)
+    }
+}
+
+impl From<ExistsError> for Error {
+    fn from(err: ExistsError) -> Error {
+        Error::Exists(err)
+    }
+}
+
+impl From<InfoError> for Error {
+    fn from(err: InfoError) -> Error {
+        Error::Info(err)
+    }
+}

+ 8 - 1
cli/src/error.rs

@@ -1,10 +1,11 @@
 use ffsend_api::action::download::Error as DownloadError;
-use ffsend_api::action::info::Error as InfoError;
 use ffsend_api::action::params::Error as ParamsError;
 use ffsend_api::action::password::Error as PasswordError;
 use ffsend_api::action::upload::Error as UploadError;
 use ffsend_api::file::remote_file::FileParseError;
 
+use action::info::Error as InfoError;
+
 #[derive(Fail, Debug)]
 pub enum Error {
     /// An error occurred while invoking an action.
@@ -12,6 +13,12 @@ pub enum Error {
     Action(#[cause] ActionError),
 }
 
+impl From<InfoError> for Error {
+    fn from(err: InfoError) -> Error {
+        Error::Action(ActionError::Info(err))
+    }
+}
+
 impl From<ActionError> for Error {
     fn from(err: ActionError) -> Error {
         Error::Action(err)