Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ name = "torrentmanager"
path = "src/main.rs"

[dependencies]
askama = "0.14.0"
askama = "0.15"
# askama_web::WebTemplate implements axum::IntoResponse
askama_web = { version = "0.14.6", features = ["axum-0.8"] }
askama_web = { version = "0.15", features = ["axum-0.8"] }
axum = { version = "0.8.4", features = ["macros"] }
axum-extra = { version = "0.12.1", features = ["cookie"] }
# UTF-8 paths for easier String/PathBuf interop
Expand Down
75 changes: 33 additions & 42 deletions src/database/content_folder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use camino::Utf8PathBuf;
use chrono::Utc;
use sea_orm::entity::prelude::*;
use sea_orm::*;
Expand Down Expand Up @@ -173,54 +174,44 @@ impl ContentFolderOperator {

Ok(model)
}
}

pub async fn ancestors(
&self,
folder: &Model,
) -> Result<ContentFolderAncestors, ContentFolderError> {
let mut ancestors = ContentFolderAncestors::default();

// Fetch the parent model
ancestors.parent = {
let Some(parent_id) = folder.parent_id else {
// No parent, no ancestors
return Ok(ancestors);
};

Some(self.find_by_id(parent_id).await?)
};
#[derive(Clone, Debug, PartialEq)]
pub struct PathBreadcrumb {
pub name: String,
pub path: String,
}

ancestors.breadcrumbs.push(PathBreadcrumb {
name: ancestors.parent.as_ref().unwrap().name.to_string(),
path: ancestors.parent.as_ref().unwrap().path.to_string(),
impl PathBreadcrumb {
/// Produce a list of path ancestors, ordered by descending order (parent -> child).
///
/// This includes the current category/folder.
///
/// The path may contain leading/trailing slashes, but any sufficiently
/// weirder path may produce unexpected results.
pub fn for_filesystem_path(path: &str) -> Vec<PathBreadcrumb> {
let path = path.trim_start_matches("/").trim_end_matches("/");
let mut breadcrumbs = vec![];
let mut path = Utf8PathBuf::from(path.to_string());

log::info!("{path}");
breadcrumbs.push(PathBreadcrumb {
name: path.file_name().unwrap().to_string(),
path: path.to_string(),
});

let mut next_id = ancestors.parent.as_ref().unwrap().parent_id;
while let Some(id) = next_id {
let folder = self.find_by_id(id).await?;
ancestors.breadcrumbs.push(PathBreadcrumb {
name: folder.name,
path: folder.path,
while path.pop() {
if path.as_str().is_empty() {
break;
}

log::info!("{:?}", path.file_name());
breadcrumbs.push(PathBreadcrumb {
name: path.file_name().unwrap().to_string(),
path: path.to_string(),
});
next_id = folder.parent_id;
}

// We walked from the bottom to the top of the folder hierarchy,
// but we want breadcrumbs navigation the other way around.
ancestors.breadcrumbs.reverse();

Ok(ancestors)
breadcrumbs.into_iter().rev().collect()
}
}

#[derive(Debug, Default)]
pub struct ContentFolderAncestors {
pub parent: Option<Model>,
pub breadcrumbs: Vec<PathBreadcrumb>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct PathBreadcrumb {
pub name: String,
pub path: String,
}
21 changes: 11 additions & 10 deletions src/extractors/folder_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ use snafu::prelude::*;

use crate::database::category::{self, CategoryOperator};
use crate::database::content_folder::{self, ContentFolderOperator, PathBreadcrumb};
use crate::filesystem::FileSystemEntry;
use crate::state::AppState;
use crate::state::error::*;

#[derive(Clone, Debug)]
pub struct FolderRequest {
pub category: category::Model,
pub folder: content_folder::Model,
pub sub_folders: Vec<content_folder::Model>,
pub ancestors: Vec<PathBreadcrumb>,
pub parent: Option<content_folder::Model>,
pub children: Vec<FileSystemEntry>,
pub breadcrumbs: Vec<PathBreadcrumb>,
}

impl FromRequestParts<AppState> for FolderRequest {
Expand Down Expand Up @@ -53,17 +53,18 @@ impl FromRequestParts<AppState> for FolderRequest {
.await
.context(CategorySnafu)?;

let ancestors = content_folder_operator
.ancestors(&current_content_folder)
.await
.context(ContentFolderSnafu)?;
let children = FileSystemEntry::from_content_folders(&category, &sub_content_folders);

let breadcrumbs = PathBreadcrumb::for_filesystem_path(&format!(
"{}{}",
category.name, current_content_folder.path
));

Ok(Self {
category,
folder: current_content_folder,
sub_folders: sub_content_folders,
ancestors: ancestors.breadcrumbs,
parent: ancestors.parent,
children,
breadcrumbs,
})
}
}
43 changes: 43 additions & 0 deletions src/filesystem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::database::{category, content_folder};

#[derive(Clone, Debug)]
pub struct FileSystemEntry {
pub name: String,
pub extra: Option<String>,
pub folder_path: String,
}

impl FileSystemEntry {
pub fn from_category(category: &category::Model) -> Self {
Self {
name: category.name.to_string(),
extra: Some(category.path.to_string()),
folder_path: category.name.to_string(),
}
}

pub fn from_categories(categories: &[category::Model]) -> Vec<Self> {
categories.iter().map(Self::from_category).collect()
}

pub fn from_content_folder(
category: &category::Model,
content_folder: &content_folder::Model,
) -> Self {
Self {
name: content_folder.name.to_string(),
extra: None,
folder_path: format!("{}{}", category.name, content_folder.path),
}
}

pub fn from_content_folders(
category: &category::Model,
content_folders: &[content_folder::Model],
) -> Vec<Self> {
content_folders
.iter()
.map(|x| Self::from_content_folder(category, x))
.collect()
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use static_serve::embed_assets;
pub mod config;
pub mod database;
pub mod extractors;
pub mod filesystem;
pub mod middleware;
pub mod migration;
pub mod routes;
Expand All @@ -30,6 +31,7 @@ pub fn router(state: state::AppState) -> Router {
"/folders/{category_name}/{*folder_path}",
get(routes::content_folder::show),
)
.route("/folders", get(routes::index::index))
.route("/folders", post(routes::content_folder::create))
.route("/logs", get(routes::logs::index))
// Register static assets routes
Expand Down
14 changes: 11 additions & 3 deletions src/routes/category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use snafu::prelude::*;

use crate::database::category;
use crate::database::category::CategoryError;
use crate::database::content_folder;
use crate::database::content_folder::PathBreadcrumb;
use crate::extractors::normalized_path::*;
use crate::filesystem::FileSystemEntry;
use crate::state::flash_message::{OperationStatus, get_cookie};
use crate::state::{AppStateContext, error::*};

Expand Down Expand Up @@ -100,11 +101,13 @@ pub struct CategoryShowTemplate {
/// Global application state
pub state: AppStateContext,
/// Categories found in database
pub content_folders: Vec<content_folder::Model>,
pub children: Vec<FileSystemEntry>,
/// Category
category: category::Model,
/// Operation status for UI confirmation (Cookie)
pub flash: Option<OperationStatus>,
/// Breadcrumbs navigation
pub breadcrumbs: Vec<PathBreadcrumb>,
}

pub async fn show(
Expand All @@ -125,15 +128,20 @@ pub async fn show(
.await
.context(CategorySnafu)?;

let children = FileSystemEntry::from_content_folders(&category, &content_folders);

let (jar, operation_status) = get_cookie(jar);

let breadcrumbs = PathBreadcrumb::for_filesystem_path(category.name.as_str());

Ok((
jar,
CategoryShowTemplate {
content_folders,
category,
children,
state: context,
flash: operation_status,
breadcrumbs,
},
))
}
12 changes: 5 additions & 7 deletions src/routes/content_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use snafu::prelude::*;
use crate::database::content_folder::PathBreadcrumb;
use crate::database::{category, content_folder};
use crate::extractors::folder_request::FolderRequest;
use crate::filesystem::FileSystemEntry;
use crate::state::flash_message::{OperationStatus, get_cookie};
use crate::state::{AppStateContext, error::*};

Expand All @@ -29,13 +30,11 @@ pub struct ContentFolderShowTemplate {
/// current folder
pub current_content_folder: content_folder::Model,
/// Folders with parent_id set to current folder
pub sub_content_folders: Vec<content_folder::Model>,
pub children: Vec<FileSystemEntry>,
/// Category
pub category: category::Model,
/// BreadCrumb extract from path
pub breadcrumb_items: Vec<PathBreadcrumb>,
/// Parent Folder if exist. If None, the parent is category
pub parent_folder: Option<content_folder::Model>,
pub breadcrumbs: Vec<PathBreadcrumb>,
/// Operation status for UI confirmation (Cookie)
pub flash: Option<OperationStatus>,
}
Expand All @@ -50,9 +49,8 @@ pub async fn show(
Ok((
jar,
ContentFolderShowTemplate {
parent_folder: folder.parent,
breadcrumb_items: folder.ancestors,
sub_content_folders: folder.sub_folders,
breadcrumbs: folder.breadcrumbs,
children: folder.children,
current_content_folder: folder.folder,
category: folder.category,
state: context,
Expand Down
Loading