1use std::fs::File;
6use std::io::{BufRead, BufReader, Seek, SeekFrom};
7use std::ops::Index;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::sync::atomic::{self, AtomicBool, AtomicUsize, Ordering};
11
12use embedder_traits::{
13 EmbedderControlId, EmbedderControlResponse, FilePickerRequest, GenericEmbedderProxy,
14 SelectedFile,
15};
16use headers::{ContentLength, ContentRange, ContentType, HeaderMap, HeaderMapExt, Range};
17use ipc_channel::ipc::IpcSender;
18use log::warn;
19use mime::Mime;
20use net_traits::blob_url_store::{BlobBuf, BlobTokenCommunicator, BlobURLStoreError};
21use net_traits::filemanager_thread::{
22 FileManagerResult, FileManagerThreadError, FileManagerThreadMsg, FileTokenCheck,
23 GetTokenForFileReply, ReadFileProgress, RelativePos,
24};
25use net_traits::response::{Response, ResponseBody};
26use parking_lot::{Mutex, RwLock};
27use rustc_hash::{FxHashMap, FxHashSet};
28use servo_arc::Arc as ServoArc;
29use servo_base::generic_channel::GenericSender;
30use servo_url::ImmutableOrigin;
31use tokio::io::{AsyncReadExt, AsyncSeekExt};
32use tokio::sync::mpsc::UnboundedSender as TokioSender;
33use tokio::task::yield_now;
34use uuid::Uuid;
35
36use crate::async_runtime::spawn_task;
37use crate::embedder::NetToEmbedderMsg;
38use crate::fetch::methods::{CancellationListener, Data, RangeRequestBounds};
39use crate::protocols::get_range_request_bounds;
40
41pub const FILE_CHUNK_SIZE: usize = 32768; struct FileStoreEntry {
45 origin: ImmutableOrigin,
47 file_impl: FileImpl,
49 refs: AtomicUsize,
54 is_valid_url: AtomicBool,
58 outstanding_tokens: FxHashSet<Uuid>,
61}
62
63#[derive(Clone)]
64struct FileMetaData {
65 path: PathBuf,
66 size: u64,
67}
68
69#[derive(Clone)]
71enum FileImpl {
72 MetaDataOnly(FileMetaData),
74 Memory(BlobBuf),
76 Sliced(Uuid, RelativePos),
79}
80
81#[derive(Clone)]
82pub struct FileManager {
83 embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
84 store: Arc<FileManagerStore>,
85 blob_token_communicator: Arc<Mutex<BlobTokenCommunicator>>,
86}
87
88impl FileManager {
89 pub fn new(
90 embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
91 blob_token_communicator: Arc<Mutex<BlobTokenCommunicator>>,
92 ) -> FileManager {
93 FileManager {
94 embedder_proxy,
95 store: Arc::new(FileManagerStore::new()),
96 blob_token_communicator,
97 }
98 }
99
100 fn read_file(
101 &self,
102 sender: IpcSender<FileManagerResult<ReadFileProgress>>,
103 id: Uuid,
104 origin: ImmutableOrigin,
105 ) {
106 let store = self.store.clone();
107 spawn_task(async move {
108 if let Err(e) = store.try_read_file(&sender, id, origin).await {
109 let _ = sender.send(Err(FileManagerThreadError::BlobURLStoreError(e)));
110 }
111 });
112 }
113
114 pub(crate) fn get_token_for_file(&self, file_id: &Uuid, allow_revoked: bool) -> FileTokenCheck {
115 self.store.get_token_for_file(file_id, allow_revoked)
116 }
117
118 pub(crate) fn invalidate_token(&self, token: &FileTokenCheck, file_id: &Uuid) {
119 self.store.invalidate_token(token, file_id);
120 }
121
122 #[expect(clippy::too_many_arguments)]
126 pub(crate) fn fetch_file(
127 &self,
128 done_sender: &mut TokioSender<Data>,
129 cancellation_listener: Arc<CancellationListener>,
130 id: Uuid,
131 file_token: &FileTokenCheck,
132 origin: ImmutableOrigin,
133 response: &mut Response,
134 range: Option<Range>,
135 ) -> Result<(), BlobURLStoreError> {
136 self.fetch_blob_buf(
137 done_sender,
138 cancellation_listener,
139 &id,
140 file_token,
141 &origin,
142 BlobBounds::Unresolved(range),
143 response,
144 )
145 }
146
147 pub fn promote_memory(
148 &self,
149 id: Uuid,
150 blob_buf: BlobBuf,
151 set_valid: bool,
152 origin: ImmutableOrigin,
153 ) {
154 self.store.promote_memory(id, blob_buf, set_valid, origin);
155 }
156
157 pub fn handle(&self, msg: FileManagerThreadMsg) {
159 match msg {
160 FileManagerThreadMsg::SelectFiles(control_id, file_picker_request, response_sender) => {
161 let store = self.store.clone();
162 let embedder = self.embedder_proxy.clone();
163 spawn_task(async move {
164 let embedder_control_msg = store
165 .select_files(control_id, file_picker_request, embedder)
166 .await;
167 response_sender.send(embedder_control_msg).unwrap();
168 });
169 },
170 FileManagerThreadMsg::ReadFile(sender, id, origin) => {
171 self.read_file(sender, id, origin);
172 },
173 FileManagerThreadMsg::PromoteMemory(id, blob_buf, set_valid, origin) => {
174 self.promote_memory(id, blob_buf, set_valid, origin);
175 },
176 FileManagerThreadMsg::AddSlicedURLEntry(id, rel_pos, sender, origin) => {
177 self.store.add_sliced_url_entry(id, rel_pos, sender, origin);
178 },
179 FileManagerThreadMsg::DecRef(id, origin, sender) => {
180 let _ = sender.send(self.store.dec_ref(&id, &origin));
181 },
182 FileManagerThreadMsg::RevokeBlobURL(id, origin, sender) => {
183 let _ = sender.send(self.store.set_blob_url_validity(false, &id, &origin));
184 },
185 FileManagerThreadMsg::ActivateBlobURL(id, sender, origin) => {
186 let _ = sender.send(self.store.set_blob_url_validity(true, &id, &origin));
187 },
188 FileManagerThreadMsg::GetTokenForFile(id, _origin, sender) => {
189 let token = match self.get_token_for_file(&id, false) {
190 FileTokenCheck::Required(token) => Some(token),
191 _ => None,
192 };
193
194 let communicator = self.blob_token_communicator.lock();
195 let _ = sender.send(GetTokenForFileReply {
196 token,
197 revoke_sender: communicator.revoke_sender.clone(),
198 refresh_sender: communicator.refresh_token_sender.clone(),
199 });
200 },
201 FileManagerThreadMsg::RevokeTokenForFile(token, id) => {
202 self.invalidate_token(&FileTokenCheck::Required(token), &id);
203 },
204 }
205 }
206
207 pub fn fetch_file_in_chunks(
208 &self,
209 done_sender: &mut TokioSender<Data>,
210 mut reader: BufReader<File>,
211 res_body: ServoArc<Mutex<ResponseBody>>,
212 cancellation_listener: Arc<CancellationListener>,
213 range: RelativePos,
214 ) {
215 let done_sender = done_sender.clone();
216 spawn_task(async move {
217 loop {
218 if cancellation_listener.cancelled() {
219 *res_body.lock() = ResponseBody::Done(vec![]);
220 let _ = done_sender.send(Data::Cancelled);
221 return;
222 }
223 let length = {
224 let buffer = reader.fill_buf().unwrap().to_vec();
225 let mut buffer_len = buffer.len();
226 if let ResponseBody::Receiving(ref mut body) = *res_body.lock() {
227 let offset = usize::min(
228 {
229 if let Some(end) = range.end {
230 let remaining_bytes =
234 end as usize - range.start as usize - body.len() + 1;
235 if remaining_bytes <= FILE_CHUNK_SIZE {
236 buffer_len = 0;
239 remaining_bytes
240 } else {
241 FILE_CHUNK_SIZE
242 }
243 } else {
244 FILE_CHUNK_SIZE
245 }
246 },
247 buffer.len(),
248 );
249 let chunk = &buffer[0..offset];
250 body.extend_from_slice(chunk);
251 let _ = done_sender.send(Data::Payload(chunk.to_vec()));
252 }
253 buffer_len
254 };
255 if length == 0 {
256 let mut body = res_body.lock();
257 let completed_body = match *body {
258 ResponseBody::Receiving(ref mut body) => std::mem::take(body),
259 _ => vec![],
260 };
261 *body = ResponseBody::Done(completed_body);
262 let _ = done_sender.send(Data::Done);
263 break;
264 }
265 reader.consume(length);
266 yield_now().await
267 }
268 });
269 }
270
271 #[expect(clippy::too_many_arguments)]
272 fn fetch_blob_buf(
273 &self,
274 done_sender: &mut TokioSender<Data>,
275 cancellation_listener: Arc<CancellationListener>,
276 id: &Uuid,
277 file_token: &FileTokenCheck,
278 origin_in: &ImmutableOrigin,
279 bounds: BlobBounds,
280 response: &mut Response,
281 ) -> Result<(), BlobURLStoreError> {
282 let file_impl = self.store.get_impl(id, file_token, origin_in)?;
283 let mut is_range_requested = false;
287 match file_impl {
288 FileImpl::Memory(buf) => {
289 let bounds = match bounds {
290 BlobBounds::Unresolved(range) => {
291 if range.is_some() {
292 is_range_requested = true;
293 }
294 get_range_request_bounds(range, buf.size)
295 },
296 BlobBounds::Resolved(bounds) => bounds,
297 };
298 let range = bounds
299 .get_final(Some(buf.size))
300 .map_err(|_| BlobURLStoreError::InvalidRange)?;
301
302 let range = range.to_abs_blob_range(buf.size as usize);
303 let len = range.len() as u64;
304 let content_range = if is_range_requested {
305 ContentRange::bytes(range.start as u64..range.end as u64, buf.size).ok()
306 } else {
307 None
308 };
309
310 set_blob_response_headers(
311 &mut response.headers,
312 len,
313 buf.type_string.parse().unwrap_or(mime::TEXT_PLAIN),
314 content_range,
315 );
316
317 let mut bytes = vec![];
318 bytes.extend_from_slice(buf.bytes.index(range));
319
320 let _ = done_sender.send(Data::Payload(bytes));
321 let _ = done_sender.send(Data::Done);
322
323 Ok(())
324 },
325 FileImpl::MetaDataOnly(metadata) => {
326 let file = File::open(&metadata.path)
333 .map_err(|e| BlobURLStoreError::External(e.to_string()))?;
334 let mut is_range_requested = false;
335 let bounds = match bounds {
336 BlobBounds::Unresolved(range) => {
337 if range.is_some() {
338 is_range_requested = true;
339 }
340 get_range_request_bounds(range, metadata.size)
341 },
342 BlobBounds::Resolved(bounds) => bounds,
343 };
344 let range = bounds
345 .get_final(Some(metadata.size))
346 .map_err(|_| BlobURLStoreError::InvalidRange)?;
347
348 let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
349 if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
350 return Err(BlobURLStoreError::External(
351 "Unexpected method for blob".into(),
352 ));
353 }
354
355 let content_range = if is_range_requested {
356 let abs_range = range.to_abs_blob_range(metadata.size as usize);
357 ContentRange::bytes(abs_range.start as u64..abs_range.end as u64, metadata.size)
358 .ok()
359 } else {
360 None
361 };
362 set_blob_response_headers(
363 &mut response.headers,
364 metadata.size,
365 mime_guess::from_path(metadata.path)
366 .first()
367 .unwrap_or(mime::TEXT_PLAIN),
368 content_range,
369 );
370
371 self.fetch_file_in_chunks(
372 &mut done_sender.clone(),
373 reader,
374 response.body.clone(),
375 cancellation_listener,
376 range,
377 );
378
379 Ok(())
380 },
381 FileImpl::Sliced(parent_id, inner_rel_pos) => {
382 let bounds = RangeRequestBounds::Final(
385 RelativePos::full_range().slice_inner(&inner_rel_pos),
386 );
387 self.fetch_blob_buf(
388 done_sender,
389 cancellation_listener,
390 &parent_id,
391 file_token,
392 origin_in,
393 BlobBounds::Resolved(bounds),
394 response,
395 )
396 },
397 }
398 }
399}
400
401enum BlobBounds {
402 Unresolved(Option<Range>),
403 Resolved(RangeRequestBounds),
404}
405
406struct FileManagerStore {
410 entries: RwLock<FxHashMap<Uuid, FileStoreEntry>>,
411}
412
413impl FileManagerStore {
414 fn new() -> Self {
415 FileManagerStore {
416 entries: RwLock::new(FxHashMap::default()),
417 }
418 }
419
420 fn get_impl(
422 &self,
423 id: &Uuid,
424 file_token: &FileTokenCheck,
425 origin_in: &ImmutableOrigin,
426 ) -> Result<FileImpl, BlobURLStoreError> {
427 match self.entries.read().get(id) {
428 Some(entry) => {
429 if *origin_in != entry.origin {
430 Err(BlobURLStoreError::InvalidOrigin)
431 } else {
432 match file_token {
433 FileTokenCheck::NotRequired => Ok(entry.file_impl.clone()),
434 FileTokenCheck::Required(token) => {
435 if entry.outstanding_tokens.contains(token) {
436 return Ok(entry.file_impl.clone());
437 }
438 Err(BlobURLStoreError::InvalidFileID)
439 },
440 FileTokenCheck::ShouldFail => Err(BlobURLStoreError::InvalidFileID),
441 }
442 }
443 },
444 None => Err(BlobURLStoreError::InvalidFileID),
445 }
446 }
447
448 fn invalidate_token(&self, token: &FileTokenCheck, file_id: &Uuid) {
449 if let FileTokenCheck::Required(token) = token {
450 let mut entries = self.entries.write();
451 if let Some(entry) = entries.get_mut(file_id) {
452 entry.outstanding_tokens.remove(token);
453
454 let zero_refs = entry.refs.load(Ordering::Acquire) == 0;
456
457 let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
459
460 let valid = entry.is_valid_url.load(Ordering::Acquire);
462
463 let do_remove = zero_refs && no_outstanding_tokens && !valid;
465
466 if do_remove {
467 entries.remove(file_id);
468 }
469 }
470 }
471 }
472
473 pub(crate) fn get_token_for_file(&self, file_id: &Uuid, allow_revoked: bool) -> FileTokenCheck {
474 let mut entries = self.entries.write();
475 let parent_id = match entries.get(file_id) {
476 Some(entry) => {
477 if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl {
478 Some(*parent_id)
479 } else {
480 None
481 }
482 },
483 None => return FileTokenCheck::ShouldFail,
484 };
485 let file_id = parent_id.as_ref().unwrap_or(file_id);
486
487 if let Some(entry) = entries.get_mut(file_id) {
488 if !allow_revoked && !entry.is_valid_url.load(Ordering::Acquire) {
489 log::warn!("Refusing to grant token for revoked blob url: {file_id:?}");
490 return FileTokenCheck::ShouldFail;
491 }
492 let token = Uuid::new_v4();
493 entry.outstanding_tokens.insert(token);
494 return FileTokenCheck::Required(token);
495 }
496 FileTokenCheck::ShouldFail
497 }
498
499 fn insert(&self, id: Uuid, entry: FileStoreEntry) {
500 self.entries.write().insert(id, entry);
501 }
502
503 fn remove(&self, id: &Uuid) {
504 self.entries.write().remove(id);
505 }
506
507 fn inc_ref(&self, id: &Uuid, origin_in: &ImmutableOrigin) -> Result<(), BlobURLStoreError> {
508 match self.entries.read().get(id) {
509 Some(entry) => {
510 if entry.origin == *origin_in {
511 entry.refs.fetch_add(1, Ordering::Relaxed);
512 Ok(())
513 } else {
514 Err(BlobURLStoreError::InvalidOrigin)
515 }
516 },
517 None => Err(BlobURLStoreError::InvalidFileID),
518 }
519 }
520
521 fn add_sliced_url_entry(
522 &self,
523 parent_id: Uuid,
524 rel_pos: RelativePos,
525 sender: GenericSender<Result<Uuid, BlobURLStoreError>>,
526 origin_in: ImmutableOrigin,
527 ) {
528 match self.inc_ref(&parent_id, &origin_in) {
529 Ok(_) => {
530 let new_id = Uuid::new_v4();
531 self.insert(
532 new_id,
533 FileStoreEntry {
534 origin: origin_in,
535 file_impl: FileImpl::Sliced(parent_id, rel_pos),
536 refs: AtomicUsize::new(1),
537 is_valid_url: AtomicBool::new(true),
540 outstanding_tokens: Default::default(),
541 },
542 );
543
544 let _ = sender.send(Ok(new_id));
546 },
547 Err(e) => {
548 let _ = sender.send(Err(e));
549 },
550 }
551 }
552
553 async fn select_files(
554 &self,
555 control_id: EmbedderControlId,
556 file_picker_request: FilePickerRequest,
557 embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
558 ) -> EmbedderControlResponse {
559 let (sender, receiver) = tokio::sync::oneshot::channel();
560
561 let origin = file_picker_request.origin.clone();
562 embedder_proxy.send(NetToEmbedderMsg::SelectFiles(
563 control_id,
564 file_picker_request,
565 sender,
566 ));
567
568 let paths = match receiver.await {
569 Ok(Some(result)) => result,
570 Ok(None) => {
571 return EmbedderControlResponse::FilePicker(None);
572 },
573 Err(error) => {
574 warn!("Failed to receive files from embedder ({:?}).", error);
575 return EmbedderControlResponse::FilePicker(None);
576 },
577 };
578
579 let mut failed = false;
580 let files: Vec<_> = paths
581 .into_iter()
582 .filter_map(|path| match self.create_entry(&path, origin.clone()) {
583 Ok(entry) => Some(entry),
584 Err(error) => {
585 failed = true;
586 warn!("Failed to create entry for selected file: {error:?}");
587 None
588 },
589 })
590 .collect();
591
592 if failed {
599 for file in files.iter() {
600 self.remove(&file.id);
601 }
602 return EmbedderControlResponse::FilePicker(Some(Vec::new()));
603 }
604
605 EmbedderControlResponse::FilePicker(Some(files))
606 }
607
608 fn create_entry(
609 &self,
610 file_path: &Path,
611 origin: ImmutableOrigin,
612 ) -> Result<SelectedFile, FileManagerThreadError> {
613 use net_traits::filemanager_thread::FileManagerThreadError::FileSystemError;
614
615 let file = File::open(file_path).map_err(|e| FileSystemError(e.to_string()))?;
616 let metadata = file
617 .metadata()
618 .map_err(|e| FileSystemError(e.to_string()))?;
619 let modified = metadata
620 .modified()
621 .map_err(|e| FileSystemError(e.to_string()))?;
622 let file_size = metadata.len();
623 let file_name = file_path
624 .file_name()
625 .ok_or(FileSystemError("Invalid filepath".to_string()))?;
626
627 let file_impl = FileImpl::MetaDataOnly(FileMetaData {
628 path: file_path.to_path_buf(),
629 size: file_size,
630 });
631
632 let id = Uuid::new_v4();
633
634 self.insert(
635 id,
636 FileStoreEntry {
637 origin,
638 file_impl,
639 refs: AtomicUsize::new(1),
640 is_valid_url: AtomicBool::new(false),
642 outstanding_tokens: Default::default(),
643 },
644 );
645
646 let filename_path = Path::new(file_name);
647 let type_string = match mime_guess::from_path(filename_path).first() {
648 Some(x) => format!("{}", x),
649 None => "".to_string(),
650 };
651
652 Ok(SelectedFile {
653 id,
654 filename: filename_path.to_path_buf(),
655 modified,
656 size: file_size,
657 type_string,
658 })
659 }
660
661 async fn get_blob_buf(
662 &self,
663 sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
664 id: &Uuid,
665 file_token: &FileTokenCheck,
666 origin_in: &ImmutableOrigin,
667 rel_pos: RelativePos,
668 ) -> Result<(), BlobURLStoreError> {
669 let file_impl = self.get_impl(id, file_token, origin_in)?;
670 match file_impl {
671 FileImpl::Memory(buf) => {
672 let range = rel_pos.to_abs_range(buf.size as usize);
673 let buf = BlobBuf {
674 filename: None,
675 type_string: buf.type_string,
676 size: range.len() as u64,
677 bytes: buf.bytes.index(range).to_vec(),
678 };
679
680 let _ = sender.send(Ok(ReadFileProgress::Meta(buf)));
681 let _ = sender.send(Ok(ReadFileProgress::EOF));
682
683 Ok(())
684 },
685 FileImpl::MetaDataOnly(metadata) => {
686 let opt_filename = metadata
693 .path
694 .file_name()
695 .and_then(|osstr| osstr.to_str())
696 .map(|s| s.to_string());
697
698 let mime = mime_guess::from_path(metadata.path.clone()).first();
699 let range = rel_pos.to_abs_range(metadata.size as usize);
700
701 let mut file = tokio::fs::File::open(&metadata.path)
702 .await
703 .map_err(|e| BlobURLStoreError::External(e.to_string()))?;
704 let seeked_start = file
705 .seek(SeekFrom::Start(range.start as u64))
706 .await
707 .map_err(|e| BlobURLStoreError::External(e.to_string()))?;
708
709 if seeked_start == (range.start as u64) {
710 let type_string = match mime {
711 Some(x) => format!("{}", x),
712 None => "".to_string(),
713 };
714
715 read_file_in_chunks(sender, file, range.len(), opt_filename, type_string).await;
716 Ok(())
717 } else {
718 Err(BlobURLStoreError::InvalidEntry)
719 }
720 },
721 FileImpl::Sliced(parent_id, inner_rel_pos) => {
722 Box::pin(self.get_blob_buf(
725 sender,
726 &parent_id,
727 file_token,
728 origin_in,
729 rel_pos.slice_inner(&inner_rel_pos),
730 ))
731 .await
732 },
733 }
734 }
735
736 async fn try_read_file(
738 &self,
739 sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
740 id: Uuid,
741 origin_in: ImmutableOrigin,
742 ) -> Result<(), BlobURLStoreError> {
743 self.get_blob_buf(
744 sender,
745 &id,
746 &FileTokenCheck::NotRequired,
747 &origin_in,
748 RelativePos::full_range(),
749 )
750 .await
751 }
752
753 fn dec_ref(&self, id: &Uuid, origin_in: &ImmutableOrigin) -> Result<(), BlobURLStoreError> {
754 let (do_remove, opt_parent_id) = match self.entries.read().get(id) {
755 Some(entry) => {
756 if entry.origin == *origin_in {
757 let old_refs = entry.refs.fetch_sub(1, Ordering::Release);
758
759 if old_refs > 1 {
760 (false, None)
762 } else {
763 let is_valid = entry.is_valid_url.load(Ordering::Acquire);
766
767 let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
769
770 let do_remove = !is_valid && no_outstanding_tokens;
772
773 if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl {
774 (do_remove, Some(*parent_id))
775 } else {
776 (do_remove, None)
777 }
778 }
779 } else {
780 return Err(BlobURLStoreError::InvalidOrigin);
781 }
782 },
783 None => return Err(BlobURLStoreError::InvalidFileID),
784 };
785
786 if do_remove {
789 atomic::fence(Ordering::Acquire);
790 self.remove(id);
791
792 if let Some(parent_id) = opt_parent_id {
793 return self.dec_ref(&parent_id, origin_in);
794 }
795 }
796
797 Ok(())
798 }
799
800 fn promote_memory(
801 &self,
802 id: Uuid,
803 blob_buf: BlobBuf,
804 set_valid: bool,
805 origin: ImmutableOrigin,
806 ) {
807 self.insert(
808 id,
809 FileStoreEntry {
810 origin,
811 file_impl: FileImpl::Memory(blob_buf),
812 refs: AtomicUsize::new(1),
813 is_valid_url: AtomicBool::new(set_valid),
814 outstanding_tokens: Default::default(),
815 },
816 );
817 }
818
819 fn set_blob_url_validity(
820 &self,
821 validity: bool,
822 id: &Uuid,
823 origin_in: &ImmutableOrigin,
824 ) -> Result<(), BlobURLStoreError> {
825 let (do_remove, opt_parent_id, res) = match self.entries.read().get(id) {
826 Some(entry) => {
827 if entry.origin == *origin_in {
828 entry.is_valid_url.store(validity, Ordering::Release);
829
830 if !validity {
831 let zero_refs = entry.refs.load(Ordering::Acquire) == 0;
835
836 let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
838
839 let do_remove = zero_refs && no_outstanding_tokens;
841
842 if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl {
843 (do_remove, Some(*parent_id), Ok(()))
844 } else {
845 (do_remove, None, Ok(()))
846 }
847 } else {
848 (false, None, Ok(()))
849 }
850 } else {
851 (false, None, Err(BlobURLStoreError::InvalidOrigin))
852 }
853 },
854 None => (false, None, Err(BlobURLStoreError::InvalidFileID)),
855 };
856
857 if do_remove {
858 atomic::fence(Ordering::Acquire);
859 self.remove(id);
860
861 if let Some(parent_id) = opt_parent_id {
862 return self.dec_ref(&parent_id, origin_in);
863 }
864 }
865 res
866 }
867}
868
869async fn read_file_in_chunks(
870 sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
871 mut file: tokio::fs::File,
872 size: usize,
873 opt_filename: Option<String>,
874 type_string: String,
875) {
876 let mut buf = vec![0; FILE_CHUNK_SIZE];
878 match file.read(&mut buf).await {
879 Ok(n) => {
880 buf.truncate(n);
881 let blob_buf = BlobBuf {
882 filename: opt_filename,
883 type_string,
884 size: size as u64,
885 bytes: buf,
886 };
887 let _ = sender.send(Ok(ReadFileProgress::Meta(blob_buf)));
888 },
889 Err(e) => {
890 let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string())));
891 return;
892 },
893 }
894
895 loop {
897 let mut buf = vec![0; FILE_CHUNK_SIZE];
898 match file.read(&mut buf).await {
899 Ok(0) => {
900 let _ = sender.send(Ok(ReadFileProgress::EOF));
901 return;
902 },
903 Ok(n) => {
904 buf.truncate(n);
905 let _ = sender.send(Ok(ReadFileProgress::Partial(buf)));
906 },
907 Err(e) => {
908 let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string())));
909 return;
910 },
911 }
912 }
913}
914
915fn set_blob_response_headers(
916 headers: &mut HeaderMap,
917 content_length: u64,
918 mime: Mime,
919 content_range: Option<ContentRange>,
920) {
921 headers.typed_insert(ContentLength(content_length));
922 if let Some(content_range) = content_range {
923 headers.typed_insert(content_range);
924 }
925 headers.typed_insert(ContentType::from(mime));
926}