diff --git a/src/db.rs b/src/db.rs index 83aa9c20473a96287fd942e83a4a5bf8b8d9e079..467b4aee49dfc6cfc83c4d2a05720104dd0d8e52 100644 --- a/src/db.rs +++ b/src/db.rs @@ -18,8 +18,8 @@ pub async fn add_file( ) -> Result<SqliteQueryResult, sqlx::Error> { // We need to send the filetype, but only sometimes. if filetype.is_none() { - // Filetype is none, so don't send the filetype in the sql query! - let result = sqlx::query( + // Filetype is none, so don't send the filetype in the sql query + let result = sqlx::query!( "INSERT INTO files ( file, expiry, @@ -29,24 +29,24 @@ pub async fn add_file( ip, domain) VALUES ( ?,?,?,?,?,?,? )", + file, + expiry, + adminkey, + accessed, + filesize, + ip, + domain ) - .bind(file) - .bind(expiry) - .bind(adminkey) - .bind(accessed) - .bind(filesize) - .bind(ip) - .bind(domain) .execute(sqlconn) .await; tracing::debug!("add_file.filetype.is_none(Added file to the database.)"); result } else { - // Filetype is NOT none, so send the filetype in the sql query! - let result = sqlx::query( + // Filetype is NOT none, so send the filetype in the sql query + let result = sqlx::query!( "INSERT INTO files ( file, - filetype, + filetype, expiry, adminkey, accessed, @@ -54,15 +54,15 @@ pub async fn add_file( ip, domain) VALUES ( ?,?,?,?,?,?,?,? )", + file, + filetype, + expiry, + adminkey, + accessed, + filesize, + ip, + domain ) - .bind(file) - .bind(filetype) - .bind(expiry) - .bind(adminkey) - .bind(accessed) - .bind(filesize) - .bind(ip) - .bind(domain) .execute(sqlconn) .await; tracing::debug!("add_file.filetype.not_none(Added file to the database.)"); @@ -76,14 +76,20 @@ pub async fn add_file( // This function checks if the filename actually exists in the DB, returning true if it does, false otherwise. // This /can/ return true if there are multiple files with the same name, as we're not really interested in checking for errors here. pub async fn check_filename(sqlconn: &Pool<Sqlite>, filename: String) -> bool { - let result = sqlx::query("SELECT COUNT(*) FROM files WHERE file = ? and isDeleted = 0") - .bind(&filename) - .fetch_one(sqlconn) - .await - .unwrap(); - let filecount: i32 = result.get("COUNT(*)"); - tracing::debug!("check_file(filecount: {:?})", filecount); - match filecount { + let result = sqlx::query!( + "SELECT COUNT(*) as count + FROM files + WHERE file = ? and isDeleted = 0", + filename + ) + .fetch_one(sqlconn) + .await + .unwrap(); + + tracing::debug!("check_file(filecount: {:?})", result.count); + + // Check if there are multiple files with the same name + match result.count { 0 => false, // File doesn't exist :( 1 => true, // File exists! _ => { @@ -91,25 +97,26 @@ pub async fn check_filename(sqlconn: &Pool<Sqlite>, filename: String) -> bool { tracing::info!( "Multiple files exist for file: {}, filecount: {}", filename, - filecount + result.count ); true } } } -// Check if the adminkey corresponds to a file, and return Some(file), or None. +// This function receives an adminkey, (and sqlpool) and returns a Some(String) with the filename corresponding to the adminkey. +// It returns files that haven't already been deleted, so this is a 'single-use' operation per file. pub async fn check_adminkey(sqlconn: &Pool<Sqlite>, adminkey: String) -> Option<String> { - // Make sure isDeleted = 0, so we don't try to delete files that are already deleted. - // Making this function into a 'single use' per file. - let result = sqlx::query("SELECT file FROM files WHERE adminkey = ? AND isDeleted = 0") - .bind(&adminkey) - .fetch_one(sqlconn) - .await; + let result = sqlx::query!( + "SELECT file FROM files WHERE adminkey = ? AND isDeleted = 0", + adminkey + ) + .fetch_one(sqlconn) + .await; if result.is_err() { return None; } else { - let filename: String = result.unwrap().get("file"); + let filename: String = result.unwrap().file; tracing::debug!("check_adminkey(filename: {:?})", filename); if filename.is_empty() { None @@ -119,55 +126,67 @@ pub async fn check_adminkey(sqlconn: &Pool<Sqlite>, adminkey: String) -> Option< } } -// Marking a file as deleted -pub async fn delete_file(sqlconn: &Pool<Sqlite>, filename: String) -> SqliteQueryResult { +// Marking a file as deleted in the DB (Doesn't delete the file on disk) +// This function returns a Some(u64), with the number of rows modified. +pub async fn delete_file(sqlconn: &Pool<Sqlite>, filename: String) -> Option<u64> { tracing::debug!("delete_file(adminkey: {})", filename); - let result = sqlx::query("UPDATE files SET isDeleted = 1 WHERE file = ?") - .bind(&filename) + let result = sqlx::query!("UPDATE files SET isDeleted = 1 WHERE file = ?", filename) .execute(sqlconn) .await; - result.unwrap() - // TODO: Check for row affected, and give a Result + if result.is_err() { + // If error, return none + return None; + } else { + return Some(result.unwrap().rows_affected()); + } } -// Updating a files viewcount -pub async fn update_fileview( - sqlconn: &Pool<Sqlite>, - filename: String, - accessed: i32, -) -> SqliteQueryResult { - sqlx::query("UPDATE files SET accessed = ?, views = views + 1 WHERE file = ?") - .bind(accessed) - .bind(filename) - .execute(sqlconn) - .await - .unwrap() +// Updating a files viewcount and accesstime. +// This receives the a String with the filename, i32 unix timestamp (and sqlpool) +// This returns a Some(u64), with the number of rows affected. +pub async fn update_fileview(sqlconn: &Pool<Sqlite>, filename: String, accessed: i32) -> Option<u64> { + let result = sqlx::query!( + "UPDATE files SET accessed = ?, views = views + 1 WHERE file = ?", + accessed, + filename + ) + .execute(sqlconn) + .await; - // TODO: Check for row affected, and give a Result + if result.is_err() { + // If error, return none + return None; + } else { + return Some(result.unwrap().rows_affected()); + } } // Returns the unix timestamp of the last access - 0 if unviewed. +// This doesn't do the basic error handling like the above functions, this re-write it completely. pub async fn get_accesss_time(sqlconn: &Pool<Sqlite>, filename: String) -> i32 { - let result = sqlx::query("SELECT accessed FROM files WHERE file = ?") - .bind(&filename) + let result = sqlx::query!("SELECT accessed FROM files WHERE file = ?", filename) .fetch_one(sqlconn) .await; - // TODO: We should probably handle /all/ the errors, but idk how. + if result.is_err() { // If result is an error, very likely this is the first upload of the file, so lets return the current time! + // If it isn't, we're in trouble. return SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs() as i32; + } else { + // This will only panic if the i64 doesn't fit into the i32 + // I guess I'll have to patch this in 2038 anyway. + let accesstime: i32 = result.unwrap().accessed.try_into().unwrap(); + tracing::debug!( + "get_accesss_time(filename: {}: {:?})", + filename, + accesstime.clone() + ); + accesstime as i32 } - let accesstime: i32 = result.unwrap().get("accessed"); - tracing::debug!( - "get_accesss_time(filename: {}: {:?})", - filename, - accesstime.clone() - ); - accesstime as i32 } // Generating a list of files that should be deleted diff --git a/src/main.rs b/src/main.rs index 5caa9485cfe264e3e3bcc7a06e7299c174cafec6..4fa04d55ed9c555ccec4a7591f2f52c5ea84aa04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,7 +88,7 @@ async fn serve_file(req: &mut Request, res: &mut Response) { return; } - // override the mimetype if it's part of unsafe extensions + // override the mimetype if it's part of unsafe extensions let r#unsafe = CONFIG .get_array("operations.unsafe_extensions") .expect("Couldn't find 'unsafe_extensions' in config. :("); @@ -147,7 +147,9 @@ async fn serve_file(req: &mut Request, res: &mut Response) { // TODO: Add actual file serving from the disk HERE, since salvo's built-in way breaks content-type header. // Check if nginx sendfile is enabled, because we can skip this if it is. - let nginxsendfile = CONFIG.get_bool("server.nginx_sendfile").expect("Couldn't find 'nginx_sendfile' in config. :("); + let nginxsendfile = CONFIG + .get_bool("server.nginx_sendfile") + .expect("Couldn't find 'nginx_sendfile' in config. :("); if nginxsendfile { // Add the header, and we're done. @@ -156,7 +158,7 @@ async fn serve_file(req: &mut Request, res: &mut Response) { res.add_header("X-Accel-Redirect", xsend, true).unwrap(); // We don't really need to update the content-type header, since nginx handles that (TODO: Test this lol) - return + return; } else { // If nginx sendfile is disabled, we need to render the file directly let filepath = "files/".to_string() + &filename.to_string(); @@ -164,7 +166,10 @@ async fn serve_file(req: &mut Request, res: &mut Response) { // If the content-type header is already set, we don't need to update this if res.headers().contains_key("content-type") { - tracing::debug!("content-type exists: content-type: {:?}", res.headers().get("content-type").unwrap()); + tracing::debug!( + "content-type exists: content-type: {:?}", + res.headers().get("content-type").unwrap() + ); file.send(headers, res).await; // This complains about us writing into ResBody::Stream, but it means it renders as plaintext, so who cares. res.render("");