1
- use std:: fs;
2
- use std:: fs:: File ;
3
- use std:: str:: FromStr ;
4
-
5
1
use crate :: db:: { Database , FileUpload } ;
6
2
use crate :: filesystem:: FileStore ;
7
3
pub use crate :: routes:: admin:: admin_routes;
@@ -13,14 +9,23 @@ use crate::settings::Settings;
13
9
#[ cfg( feature = "void-cat-redirects" ) ]
14
10
use crate :: void_db:: VoidCatDb ;
15
11
use anyhow:: Error ;
12
+ use http_range_header:: {
13
+ parse_range_header, EndPosition , StartPosition , SyntacticallyCorrectRange ,
14
+ } ;
16
15
use nostr:: Event ;
17
16
use rocket:: fs:: NamedFile ;
18
17
use rocket:: http:: { ContentType , Header , Status } ;
19
18
#[ cfg( feature = "void-cat-redirects" ) ]
20
19
use rocket:: response:: Redirect ;
21
20
use rocket:: response:: Responder ;
22
21
use rocket:: serde:: Serialize ;
23
- use rocket:: { Request , State } ;
22
+ use rocket:: { Request , Response , State } ;
23
+ use std:: io:: SeekFrom ;
24
+ use std:: pin:: { pin, Pin } ;
25
+ use std:: str:: FromStr ;
26
+ use std:: task:: { Context , Poll } ;
27
+ use tokio:: fs:: File ;
28
+ use tokio:: io:: { AsyncRead , AsyncSeek , ReadBuf } ;
24
29
25
30
#[ cfg( feature = "blossom" ) ]
26
31
mod blossom;
@@ -95,9 +100,93 @@ impl Nip94Event {
95
100
}
96
101
}
97
102
103
+ struct RangeBody {
104
+ pub file : File ,
105
+ pub file_size : u64 ,
106
+ pub ranges : Vec < SyntacticallyCorrectRange > ,
107
+
108
+ current_range_index : usize ,
109
+ current_offset : u64 ,
110
+ }
111
+
112
+ impl AsyncRead for RangeBody {
113
+ fn poll_read (
114
+ mut self : Pin < & mut Self > ,
115
+ cx : & mut Context < ' _ > ,
116
+ buf : & mut ReadBuf < ' _ > ,
117
+ ) -> Poll < std:: io:: Result < ( ) > > {
118
+ if self . current_range_index >= self . ranges . len ( ) {
119
+ return Poll :: Ready ( Ok ( ( ) ) ) ;
120
+ }
121
+
122
+ let current_range = & self . ranges [ self . current_range_index ] ;
123
+ let start_pos = match current_range. start {
124
+ StartPosition :: Index ( i) => i,
125
+ StartPosition :: FromLast ( i) => self . file_size - i,
126
+ } ;
127
+ let end_pos = match current_range. end {
128
+ EndPosition :: Index ( i) => i,
129
+ EndPosition :: LastByte => self . file_size ,
130
+ } ;
131
+ let range_start = start_pos + self . current_offset ;
132
+ let range_len = end_pos - range_start;
133
+ let bytes_to_read = buf. remaining ( ) . min ( range_len as usize ) as u64 ;
134
+
135
+ if bytes_to_read == 0 {
136
+ self . current_offset = 0 ;
137
+ self . current_range_index += 1 ;
138
+ return self . poll_read ( cx, buf) ;
139
+ }
140
+
141
+ let pinned = pin ! ( & mut self . file) ;
142
+ pinned. start_seek ( SeekFrom :: Start ( range_start) ) ?;
143
+
144
+ let pinned = pin ! ( & mut self . file) ;
145
+ match pinned. poll_complete ( cx) {
146
+ Poll :: Ready ( Ok ( _) ) => { }
147
+ Poll :: Ready ( Err ( e) ) => return Poll :: Ready ( Err ( e) ) ,
148
+ Poll :: Pending => return Poll :: Pending ,
149
+ }
150
+
151
+ // Read data from the file
152
+ let pinned = pin ! ( & mut self . file) ;
153
+ let n = pinned. poll_read ( cx, & mut buf. take ( bytes_to_read as usize ) ) ;
154
+ if let Poll :: Ready ( Ok ( ( ) ) ) = n {
155
+ self . current_offset += bytes_to_read;
156
+ Poll :: Ready ( Ok ( ( ) ) )
157
+ } else {
158
+ Poll :: Pending
159
+ }
160
+ }
161
+ }
162
+
98
163
impl < ' r > Responder < ' r , ' static > for FilePayload {
99
164
fn respond_to ( self , request : & ' r Request < ' _ > ) -> rocket:: response:: Result < ' static > {
100
- let mut response = self . file . respond_to ( request) ?;
165
+ let mut response = Response :: new ( ) ;
166
+
167
+ // handle ranges
168
+ #[ cfg( feature = "ranges" ) ]
169
+ {
170
+ response. set_header ( Header :: new ( "accept-ranges" , "bytes" ) ) ;
171
+ if let Some ( r) = request. headers ( ) . get ( "range" ) . next ( ) {
172
+ if let Ok ( ranges) = parse_range_header ( r) {
173
+ let r_body = RangeBody {
174
+ file_size : self . info . size , // TODO: handle filesize mismatch
175
+ file : self . file ,
176
+ ranges : ranges. ranges ,
177
+ current_range_index : 0 ,
178
+ current_offset : 0 ,
179
+ } ;
180
+ response. set_streamed_body ( Box :: pin ( r_body) ) ;
181
+ }
182
+ } else {
183
+ response. set_streamed_body ( self . file ) ;
184
+ }
185
+ }
186
+ #[ cfg( not( feature = "ranges" ) ) ]
187
+ response. set_streamed_body ( self . file ) ;
188
+ response. set_header ( Header :: new ( "content-length" , self . info . size . to_string ( ) ) ) ;
189
+
101
190
if let Ok ( ct) = ContentType :: from_str ( & self . info . mime_type ) {
102
191
response. set_header ( ct) ;
103
192
}
@@ -145,7 +234,7 @@ async fn delete_file(
145
234
if let Err ( e) = db. delete_file ( & id) . await {
146
235
return Err ( Error :: msg ( format ! ( "Failed to delete (fs): {}" , e) ) ) ;
147
236
}
148
- if let Err ( e) = fs:: remove_file ( fs. get ( & id) ) {
237
+ if let Err ( e) = tokio :: fs:: remove_file ( fs. get ( & id) ) . await {
149
238
return Err ( Error :: msg ( format ! ( "Failed to delete (fs): {}" , e) ) ) ;
150
239
}
151
240
}
@@ -189,7 +278,7 @@ pub async fn get_blob(
189
278
return Err ( Status :: NotFound ) ;
190
279
}
191
280
if let Ok ( Some ( info) ) = db. get_file ( & id) . await {
192
- if let Ok ( f) = File :: open ( fs. get ( & id) ) {
281
+ if let Ok ( f) = File :: open ( fs. get ( & id) ) . await {
193
282
return Ok ( FilePayload { file : f, info } ) ;
194
283
}
195
284
}
0 commit comments