use actix_web::{ http::header::{ContentDisposition, TryIntoHeaderValue as _, CONTENT_DISPOSITION}, HttpResponse, }; use anyhow::{anyhow, Result}; use askama::Template; use git2::Repository; use std::str; pub struct Breadcrumb { pub name: String, pub link: String, } #[derive(Template)] #[template(path = "file.html")] struct FileView<'a, 'b> { file_content: String, file_path: &'a str, repo_name: &'b str, breadcrumbs: Vec<Breadcrumb>, } pub fn get_file_breadcrumbs(repo_owner: &str, repo_name: &str, file_path: &str) -> Vec<Breadcrumb> { let mut breadcrumbs = vec![ Breadcrumb { name: String::from(".."), link: format!("/"), }, Breadcrumb { name: format!("~{}", repo_owner), link: format!("/~{}", repo_owner), }, Breadcrumb { name: repo_name.to_string(), link: format!("/~{}/{}", repo_owner, repo_name), }, ]; let mut breadcrumb_link = format!("/~{}/{}/tree", repo_owner, repo_name); for segment in file_path.split("/") { breadcrumb_link += &format!("/{}", segment); breadcrumbs.push(Breadcrumb { name: segment.to_string(), link: breadcrumb_link.clone(), }) } breadcrumbs } pub fn render_binary_object(blob: &git2::Blob, path: &str) -> HttpResponse { let path = std::path::Path::new(path); let file_name = path .file_name() .unwrap_or_default() .to_str() .unwrap_or_default(); let extension = path .extension() .unwrap_or_default() .to_str() .unwrap_or_default() .to_lowercase(); match extension.as_str() { "pdf" => HttpResponse::Ok() .content_type("application/pdf") .body(blob.content().to_owned()), "jpg" | "jpeg" => HttpResponse::Ok() .content_type("image/jpeg") .body(blob.content().to_owned()), "png" => HttpResponse::Ok() .content_type("image/png") .body(blob.content().to_owned()), _ => { let cd = ContentDisposition::attachment(file_name); let mut resp = HttpResponse::Ok().body(blob.content().to_owned()); resp.headers_mut() .insert(CONTENT_DISPOSITION, cd.try_into_value().unwrap()); return resp; } } } pub fn render_file( repo: &Repository, repo_owner: &str, repo_name: &str, file_path: &str, ) -> Result<String> { let obj = repo.revparse_single(&format!("HEAD:{}", file_path))?; let kind = obj.kind(); if kind != Some(git2::ObjectType::Blob) && kind != Some(git2::ObjectType::Tree) { return Err(anyhow!("{} is not a file or folder", file_path)); } if kind == Some(git2::ObjectType::Tree) { return Ok(format!("{} is a folder", file_path)); } if let Some(blob) = obj.as_blob() { if let Ok(blob_str) = str::from_utf8(blob.content()) { let file_view = FileView { file_content: blob_str.to_string(), file_path, repo_name, breadcrumbs: get_file_breadcrumbs(repo_owner, repo_name, file_path), }; if let Ok(x) = file_view.render() { return Ok(x); } else { return Err(anyhow!("failed to render")); } } } Err(anyhow!("could not find file")) }