@@ -4,7 +4,7 @@ use anyhow::Context;
44use chrono:: { DateTime , Utc } ;
55use sqlx:: any:: { AnyKind , AnyStatement , AnyTypeInfo } ;
66use sqlx:: postgres:: types:: PgTimeTz ;
7- use sqlx:: { Postgres , Statement , Type } ;
7+ use sqlx:: { Executor , Postgres , Statement , Type } ;
88use std:: io:: ErrorKind ;
99use std:: path:: { Component , Path , PathBuf } ;
1010
@@ -14,9 +14,9 @@ pub(crate) struct FileSystem {
1414}
1515
1616impl FileSystem {
17- pub async fn init ( local_root : & Path , db : & Database ) -> Self {
17+ pub async fn init ( local_root : impl Into < PathBuf > , db : & Database ) -> Self {
1818 Self {
19- local_root : PathBuf :: from ( local_root) ,
19+ local_root : local_root. into ( ) ,
2020 db_fs_queries : match DbFsQueries :: init ( db) . await {
2121 Ok ( q) => Some ( q) ,
2222 Err ( e) => {
@@ -53,14 +53,24 @@ impl FileSystem {
5353 }
5454 }
5555
56- pub async fn read_file ( & self , app_state : & AppState , path : & Path ) -> anyhow:: Result < String > {
56+ pub async fn read_to_string (
57+ & self ,
58+ app_state : & AppState ,
59+ path : & Path ,
60+ ) -> anyhow:: Result < String > {
61+ let bytes = self . read_file ( app_state, path) . await ?;
62+ String :: from_utf8 ( bytes)
63+ . with_context ( || format ! ( "The file at {path:?} contains invalid UTF8 characters" ) )
64+ }
65+
66+ pub async fn read_file ( & self , app_state : & AppState , path : & Path ) -> anyhow:: Result < Vec < u8 > > {
5767 let local_path = self . safe_local_path ( path) ?;
58- let local_result = tokio:: fs:: read_to_string ( & local_path) . await ;
68+ let local_result = tokio:: fs:: read ( & local_path) . await ;
5969 match ( local_result, & self . db_fs_queries ) {
6070 ( Ok ( f) , _) => Ok ( f) ,
6171 ( Err ( e) , Some ( db_fs) ) if e. kind ( ) == ErrorKind :: NotFound => {
6272 // no local file, try the database
63- db_fs. read_file ( app_state, path) . await
73+ db_fs. read_file ( app_state, path. as_ref ( ) ) . await
6474 }
6575 ( Err ( e) , _) => Err ( e) . with_context ( || format ! ( "Unable to read local file {path:?}" ) ) ,
6676 }
@@ -142,13 +152,38 @@ impl DbFsQueries {
142152 } )
143153 }
144154
145- async fn read_file ( & self , app_state : & AppState , path : & Path ) -> anyhow:: Result < String > {
155+ async fn read_file ( & self , app_state : & AppState , path : & Path ) -> anyhow:: Result < Vec < u8 > > {
146156 self . read_file
147- . query_as :: < ( String , ) > ( )
157+ . query_as :: < ( Vec < u8 > , ) > ( )
148158 . bind ( path. display ( ) . to_string ( ) )
149159 . fetch_one ( & app_state. db . connection )
150160 . await
151161 . map ( |( modified, ) | modified)
152162 . with_context ( || format ! ( "Unable to read {path:?} from the database" ) )
153163 }
154164}
165+
166+ #[ actix_web:: test]
167+ async fn test_sql_file_read_utf8 ( ) -> anyhow:: Result < ( ) > {
168+ let state = AppState :: init ( ) . await ?;
169+ state
170+ . db
171+ . connection
172+ . execute (
173+ r#"
174+ CREATE TABLE sqlpage_files(
175+ path VARCHAR(255) NOT NULL PRIMARY KEY,
176+ contents BLOB,
177+ last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
178+ );
179+ INSERT INTO sqlpage_files(path, contents) VALUES ('unit test file.txt', 'Héllö world! 😀');
180+ "# ,
181+ )
182+ . await ?;
183+ let fs = FileSystem :: init ( "/" , & state. db ) . await ;
184+ let actual = fs
185+ . read_to_string ( & state, "unit test file.txt" . as_ref ( ) )
186+ . await ?;
187+ assert_eq ! ( actual, "Héllö world! 😀" ) ;
188+ Ok ( ( ) )
189+ }
0 commit comments