@@ -6,6 +6,7 @@ use log::{LevelFilter, error, info};
66use std:: convert:: Infallible ;
77use std:: env;
88use time:: format_description;
9+ use warp:: http:: StatusCode ;
910use warp:: { Filter , http:: Response } ;
1011
1112#[ tokio:: main]
@@ -14,12 +15,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
1415 . filter_level ( LevelFilter :: Info )
1516 . init ( ) ;
1617 info ! ( "GCS Web Server starting..." ) ;
17- let bucket_name;
1818 let args: Vec < String > = env:: args ( ) . collect ( ) ;
19- match args. get ( 1 ) {
20- Some ( bucket) => bucket_name = bucket. to_string ( ) ,
19+ let bucket_name = match args. get ( 1 ) {
20+ Some ( bucket) => bucket. to_string ( ) ,
2121 None => panic ! ( "No bucket was passed" ) ,
22- }
22+ } ;
2323 info ! ( "Connecting to bucket gs://{bucket_name}" ) ;
2424
2525 let config = ClientConfig :: default ( ) . with_auth ( ) . await ?;
@@ -33,22 +33,51 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3333 . and ( bucket_filter)
3434 . and_then ( serve_gcs_content) ;
3535
36- warp:: serve ( routes) . run ( ( [ 0 , 0 , 0 , 0 ] , 8080 ) ) . await ;
36+ warp:: serve ( routes) . run ( ( [ 0 , 0 , 0 , 0 ] , 8585 ) ) . await ;
3737
3838 Ok ( ( ) )
3939}
4040
41- async fn check_for_dirs ( client : & Client , bucket : String , path : String ) -> bool {
41+ async fn check_for_dirs (
42+ client : & Client ,
43+ bucket : String ,
44+ path : String ,
45+ ) -> Option < Response < Vec < u8 > > > {
46+ if path. ends_with ( '/' ) || path. is_empty ( ) {
47+ return None ;
48+ }
4249 let list_req_test_prefix = ListObjectsRequest {
4350 bucket,
4451 prefix : Some ( format ! ( "{}/" , path) ) ,
4552 delimiter : Some ( "/" . to_string ( ) ) ,
53+ max_results : Some ( 1 ) , // Fast check: only need 1 result to confirm existence
4654 ..Default :: default ( )
4755 } ;
48- client
49- . list_objects ( & list_req_test_prefix)
50- . await
51- . is_ok_and ( |res| res. prefixes . is_some ( ) || res. items . is_some ( ) )
56+ match client. list_objects ( & list_req_test_prefix) . await {
57+ Ok ( res) => {
58+ // If any prefixes (sub-folders) or items (files) exist, it's a folder.
59+ if res. prefixes . is_some ( ) || res. items . is_some ( ) {
60+ let new_path = format ! ( "/{}/" , path) ;
61+ info ! ( "REDIRECT: path='{}' -> '{}'" , path, new_path) ;
62+
63+ // Return a 302 Found response to redirect the browser
64+ let res = Response :: builder ( )
65+ . status ( StatusCode :: FOUND )
66+ . header ( "Location" , new_path)
67+ . body ( Vec :: new ( ) )
68+ . unwrap ( ) ;
69+ return Some ( res) ;
70+ }
71+ }
72+ Err ( e) => {
73+ error ! ( "REDIRECT_CHECK_ERROR: path='{}', error='{:?}'" , path, e) ;
74+ }
75+ }
76+ None
77+ // client
78+ // .list_objects(&list_req_test_prefix)
79+ // .await
80+ // .is_ok_and(|res| res.prefixes.is_some() || res.items.is_some())
5281}
5382
5483async fn serve_gcs_content (
@@ -70,10 +99,8 @@ async fn serve_gcs_content(
7099 let file_metadata = client. get_object ( & file_req) . await ;
71100
72101 // Check if a path without a trailing slash is a directory
73- let is_dir_no_slash = if !is_dir && !path_str. is_empty ( ) {
74- check_for_dirs ( & client, bucket_name. clone ( ) , path_str. clone ( ) ) . await
75- } else {
76- false
102+ if let Some ( res) = check_for_dirs ( & client, bucket_name. clone ( ) , path_str. clone ( ) ) . await {
103+ return Ok ( res) ;
77104 } ;
78105
79106 // If metadata is found, it's a file. Serve the download.
@@ -128,7 +155,7 @@ async fn serve_gcs_content(
128155 }
129156
130157 // If it's a directory (either with or without a trailing slash), list its contents
131- if is_dir || path_str. is_empty ( ) || is_dir_no_slash {
158+ if is_dir || path_str. is_empty ( ) {
132159 let prefix = if path_str. is_empty ( ) {
133160 None
134161 } else {
@@ -224,20 +251,18 @@ fn build_html(
224251) -> String {
225252 let parent_path = if path_str. is_empty ( ) {
226253 "" . to_string ( )
254+ } else if path_str. ends_with ( "/" ) {
255+ str:: trim_end_matches ( & path_str, '/' )
256+ . rsplit_once ( '/' )
257+ . map_or ( "" , |( parent, _) | parent)
258+ . to_string ( )
227259 } else {
228- if path_str. ends_with ( "/" ) {
229- str:: trim_end_matches ( & path_str, '/' )
230- . rsplit_once ( '/' )
231- . map_or ( "" , |( parent, _) | parent)
232- . to_string ( )
233- } else {
234- path_str
235- . rsplit_once ( '/' )
236- . map_or ( "" , |( parent, _) | parent)
237- . to_string ( )
238- }
260+ path_str
261+ . rsplit_once ( '/' )
262+ . map_or ( "" , |( parent, _) | parent)
263+ . to_string ( )
239264 } ;
240- let html = format ! (
265+ format ! (
241266 r#"
242267<!DOCTYPE html>
243268<html>
@@ -286,19 +311,18 @@ fn build_html(
286311 . map( |f| format!(
287312 "<tr><td><i class=\" fa-solid fa-folder\" ></i></td> <td><a href=\" /{}\" >{}</a></td><td align=\" right\" >-</td><td align=\" right\" >-</td></tr>" ,
288313 f,
289- f. trim_end_matches( '/' ) . split( '/' ) . last ( ) . unwrap_or( "" )
314+ f. trim_end_matches( '/' ) . split( '/' ) . next_back ( ) . unwrap_or( "" )
290315 ) )
291316 . collect:: <String >( ) ,
292317 files
293318 . iter( )
294319 . map( |( name, size, updated) | format!(
295320 r#"<tr><td><i class="fa-solid fa-file"></i></td> <td><a href="/{}">{}</a></td><td align="right">{}</td><td align="right">{}</td></tr>"# ,
296321 name,
297- name. split( '/' ) . last ( ) . unwrap_or( "" ) ,
322+ name. split( '/' ) . next_back ( ) . unwrap_or( "" ) ,
298323 size,
299324 updated
300325 ) )
301326 . collect:: <String >( )
302- ) ;
303- return html;
327+ )
304328}
0 commit comments