Skip to content
This repository has been archived by the owner on Jan 21, 2024. It is now read-only.

An example to store file in DB #10

Open
Sirneij opened this issue Jan 12, 2023 · 5 comments
Open

An example to store file in DB #10

Sirneij opened this issue Jan 12, 2023 · 5 comments

Comments

@Sirneij
Copy link

Sirneij commented Jan 12, 2023

Assuming you are working with a PostgreSQL database, any example of dropping the uploaded file in a media folder at the project's root and storing a reference to the file in DB?

@tokcum
Copy link

tokcum commented Jan 22, 2023

I was so happy when I found this crate because I was struggling to implement a file upload via actix-web + actix-multipart natively.

However, I'm also wondering how I can persist the Tempfile to disk. NamedTempFile has method persist(). When using persist(), it does not compile because of cannot move.

This is my code (taken from the example and modified):

#[derive(MultipartForm)]
struct Upload {
    text: Text<String>,
    number: Text<i64>,
    file: Tempfile,
}

#[post("/")]
async fn route(form: MultipartForm<Upload>) -> impl Responder {
    let file = &form.file.file;
    let _ = file.persist("/tmp/store/hugo.dat");

    format!("Ok")
}
error[E0507]: cannot move out of `*file` which is behind a shared reference
   --> src/main.rs:18:13
    |
18  |     let _ = file.persist("/tmp/store/hugo.dat");
    |             ^^^^^------------------------------
    |             |    |
    |             |    `*file` moved due to this method call
    |             move occurs because `*file` has type `tempfile::file::NamedTempFile`, which does not implement the `Copy` trait
    |
note: this function takes ownership of the receiver `self`, which moves `*file`
   --> /home/tobias/.cargo/registry/src/github.com-1ecc6299db9ec823/tempfile-3.3.0/src/file/mod.rs:714:36
    |
714 |     pub fn persist<P: AsRef<Path>>(self, new_path: P) -> Result<File, PersistError> {

Not sure if this is solvable. Any help appreciated.

@jacob-pro
Copy link
Owner

jacob-pro commented Jan 22, 2023

Hi @tokcum

You need to have a mutable reference access to the form before you can mutate it.

One way to do this is by calling the into_inner() function on the form.

So then you should be able to do something like

#[post("/")]
async fn route(form: MultipartForm<Upload>) -> impl Responder {
    let mut form: Upload = form.into_inner();
    let _ = form.file.file.persist("/tmp/store/hugo.dat");
}

Alternatively (i haven't tested this) - but I think you should just be able to make the form mutable

#[post("/")]
async fn route(mut form: MultipartForm<Upload>) -> impl Responder {

}

@Sirneij
Copy link
Author

Sirneij commented Jan 22, 2023

Also, @tokcum, since it's a form, you can do (please refer to the comments in the code.):

#[derive(MultipartForm)]
struct Upload {
    text: Text<String>,
    number: Text<i64>,
    file: Tempfile,
}

#[post("/")]
async fn route(form: MultipartForm<Upload>) -> impl Responder {
    // get the file name
    let filename = form.0.file.file_name.as_ref().map(|m| m.as_ref()).unwrap_or("null");
    // create the directory(ies) you want it saved in, if not previously created
    // this will create a `tmp` directory in your project's root and `store` will be 
    // subdirectory
    std::fs::create_dir_all("tmp/store/").expect("Unable to create folders");
    // If you don't want file overwrites, you can prepend or affix some random strings to the 
    // filename
    let random_bytes: String = {
            let mut buff = [0_u8; 8];
            OsRng.fill_bytes(&mut buff);
            hex::encode(buff)
        };
    let filepath = format!("tmp/store/{}_{}", random_bytes, file_name);
    // If you want it saved in the DB, it is better to save a refernce to the file location
    // Like you could save something like https://localhost:8000/{filepath} to the DB

    // Then to persist, you can just do
    let _ = form.0.file.file.persist(filepath);
    // But I prefer wrapping the persistence process in `block`.
    let _ = actix_web::web::block(|| form.0.file.file.persist(filepath));
    

    format!("Ok")
}

You should be good with that. To use OsRng and hex, you need to install rand_core and hex crates.

@tokcum
Copy link

tokcum commented Jan 23, 2023

Thank you @jacob-pro for pointing out into_inner(). Btw, the mut is not required.

So, this approach works:

#[post("/")]
async fn route(form: MultipartForm<Upload>) -> impl Responder {
    let form: Upload = form.into_inner();
    let _ = form.file.file.persist("/tmp/store/hugo.dat");
}

@tokcum
Copy link

tokcum commented Jan 23, 2023

@Sirneij, thanks for your approach. It works as well. I simplified it to show the essence. So with form.0, I don't even need the call to into_inner().

#[post("/")]
async fn route(form: MultipartForm<Upload>) -> impl Responder {
    let _ = form.0.file.file.persist("/tmp/store/hugo.dat");

    format!("Ok")
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants