1use std::ffi::OsStr;
5use std::fs::{self, File};
6use std::io::{Read, Write};
7use std::path::{Path, PathBuf};
8
9use std::os::unix::ffi::OsStrExt;
10use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
11
12use crate::inspection::{
13 CacheEntryInspection, NonstandardEntryPolicy, read_thumbnail_for_inspection,
14 thumbnail_timestamps, thumbnail_timestamps_from_metadata,
15};
16use crate::{
17 AccessTimePreservation, CacheDirectoryProblem, CacheEntryHandle, CacheEntryProblem,
18 CacheNamespace, CachePathProblem, CacheRootProblem, FailureNamespace, OwnedRawThumbnailImage,
19 ParsedThumbnailPng, PersonalValidationOutcome, RawThumbnailImage,
20 ReadablePersonalOriginalIdentity, Result, SharedRelativeOriginalUri, SharedRepositoryContext,
21 SharedValidationOutcome, ThumbnailError, ThumbnailMetadata, ThumbnailSize, ThumbnailTimestamps,
22 UnixMtimeSeconds, decode_validated_thumbnail_png_to_rgba8,
23 downscaled_validated_thumbnail_png_to_rgba8, encode_rgba_png, metadata_problem,
24 normalized_personal_thumbnail_from_cache_png, normalized_personal_thumbnail_png,
25 normalized_personal_thumbnail_raw_png, push_problem, thumbnail_metadata_pairs,
26 validate_personal_thumbnail, validate_shared_thumbnail,
27};
28use crate::{PersonalOriginalIdentity, PersonalOriginalUri};
29use crate::{ThumbnailMetadataKey, ThumbnailMetadataProblemKind};
30
31#[derive(Clone, Debug, Eq, Hash, PartialEq)]
33pub struct PersonalCacheRoot {
34 path: PathBuf,
35}
36
37impl PersonalCacheRoot {
38 pub fn new(path: impl AsRef<Path>) -> Result<Self> {
44 let path = path.as_ref();
45 if !path.is_absolute() {
46 return Err(ThumbnailError::InvalidCacheRoot {
47 path: path.to_owned(),
48 problem: CacheRootProblem::NotAbsolute,
49 });
50 }
51 Ok(Self {
52 path: path.to_owned(),
53 })
54 }
55
56 pub fn resolve_from_env() -> Result<Self> {
63 let xdg_cache_home = std::env::var_os("XDG_CACHE_HOME");
64 let home = std::env::var_os("HOME");
65 Self::resolve_from_values(xdg_cache_home.as_deref(), home.as_deref())
66 }
67
68 pub fn resolve_from_values(
77 xdg_cache_home: Option<&OsStr>,
78 home: Option<&OsStr>,
79 ) -> Result<Self> {
80 if let Some(cache_home) = xdg_cache_home {
81 if !cache_home.as_bytes().is_empty() {
82 let path = PathBuf::from(cache_home);
83 if path.is_absolute() {
84 return Self::new(path.join("thumbnails"));
85 }
86 }
87 }
88
89 let Some(home) = home else {
90 return Err(ThumbnailError::cache_root_unavailable(
91 "HOME is required when XDG_CACHE_HOME is unset, blank, or relative",
92 ));
93 };
94 if home.as_bytes().is_empty() {
95 return Err(ThumbnailError::cache_root_unavailable(
96 "HOME is required when XDG_CACHE_HOME is unset, blank, or relative",
97 ));
98 }
99 let home = PathBuf::from(home);
100 if !home.is_absolute() {
101 return Err(ThumbnailError::cache_root_unavailable(
102 "HOME must be absolute",
103 ));
104 }
105 Self::new(home.join(".cache").join("thumbnails"))
106 }
107
108 #[must_use]
110 pub fn as_path(&self) -> &Path {
111 &self.path
112 }
113
114 #[must_use]
116 pub fn cache_entry_path(
117 &self,
118 uri: &PersonalOriginalUri,
119 namespace: &CacheNamespace,
120 ) -> PathBuf {
121 namespace.join_under(&self.path, &uri.thumbnail_file_name())
122 }
123
124 #[must_use]
130 pub fn cache_entry_handle(
131 &self,
132 uri: &PersonalOriginalUri,
133 namespace: &CacheNamespace,
134 ) -> CacheEntryHandle {
135 let cache_dir = match namespace {
136 CacheNamespace::Size(size) => self.path.join(size.directory_name()),
137 CacheNamespace::Failure(namespace) => self.path.join("fail").join(namespace.as_str()),
138 };
139 CacheEntryHandle::new(cache_dir, self.cache_entry_path(uri, namespace))
140 }
141
142 pub fn lookup_thumbnail_path(
153 &self,
154 original: &ReadablePersonalOriginalIdentity,
155 size: ThumbnailSize,
156 ) -> Result<PersonalThumbnailLookup<ThumbnailPathLookupEntry>> {
157 match self.lookup_thumbnail_entry(original, size)? {
158 PersonalThumbnailLookup::Valid(entry) => {
159 Ok(PersonalThumbnailLookup::Valid(ThumbnailPathLookupEntry {
160 path: entry.path,
161 metadata: entry.metadata,
162 }))
163 }
164 PersonalThumbnailLookup::Missing => Ok(PersonalThumbnailLookup::Missing),
165 PersonalThumbnailLookup::Invalid(problems) => {
166 Ok(PersonalThumbnailLookup::Invalid(problems))
167 }
168 }
169 }
170
171 pub fn lookup_thumbnail_png_bytes(
180 &self,
181 original: &ReadablePersonalOriginalIdentity,
182 size: ThumbnailSize,
183 ) -> Result<PersonalThumbnailLookup<ThumbnailPngBytesLookupEntry>> {
184 match self.lookup_thumbnail_entry(original, size)? {
185 PersonalThumbnailLookup::Valid(entry) => Ok(PersonalThumbnailLookup::Valid(
186 ThumbnailPngBytesLookupEntry {
187 path: entry.path,
188 bytes: entry.bytes,
189 metadata: entry.metadata,
190 },
191 )),
192 PersonalThumbnailLookup::Missing => Ok(PersonalThumbnailLookup::Missing),
193 PersonalThumbnailLookup::Invalid(problems) => {
194 Ok(PersonalThumbnailLookup::Invalid(problems))
195 }
196 }
197 }
198
199 pub fn lookup_thumbnail_rgba8(
209 &self,
210 original: &ReadablePersonalOriginalIdentity,
211 size: ThumbnailSize,
212 ) -> Result<PersonalThumbnailLookup<ThumbnailRgba8LookupEntry>> {
213 match self.lookup_thumbnail_entry(original, size)? {
214 PersonalThumbnailLookup::Valid(entry) => Ok(PersonalThumbnailLookup::Valid(
215 rgba8_lookup_entry_from_parts(entry.path, &entry.bytes, entry.metadata)?,
216 )),
217 PersonalThumbnailLookup::Missing => Ok(PersonalThumbnailLookup::Missing),
218 PersonalThumbnailLookup::Invalid(problems) => {
219 Ok(PersonalThumbnailLookup::Invalid(problems))
220 }
221 }
222 }
223
224 pub fn lookup_display_thumbnail_rgba8(
234 &self,
235 original: &ReadablePersonalOriginalIdentity,
236 size: ThumbnailSize,
237 ) -> Result<PersonalThumbnailLookup<DisplayThumbnailRgba8LookupEntry>> {
238 for source_size in display_candidate_sizes(size) {
239 match self.lookup_thumbnail_entry(original, source_size)? {
240 PersonalThumbnailLookup::Valid(entry) => {
241 return Ok(PersonalThumbnailLookup::Valid(
242 display_rgba8_lookup_entry_from_parts(
243 entry.path,
244 &entry.bytes,
245 entry.metadata,
246 size,
247 source_size,
248 )?,
249 ));
250 }
251 PersonalThumbnailLookup::Missing => {}
252 PersonalThumbnailLookup::Invalid(problems) => {
253 return Ok(PersonalThumbnailLookup::Invalid(problems));
254 }
255 }
256 }
257 Ok(PersonalThumbnailLookup::Missing)
258 }
259
260 pub fn install_thumbnail_returning_path(
267 &self,
268 original: &ReadablePersonalOriginalIdentity,
269 size: ThumbnailSize,
270 rendered_png: &[u8],
271 ) -> Result<InstalledThumbnailPath> {
272 let (path, _) = self.install_thumbnail_entry(original, size, rendered_png)?;
273 Ok(InstalledThumbnailPath { path })
274 }
275
276 pub fn install_thumbnail_returning_png_bytes(
283 &self,
284 original: &ReadablePersonalOriginalIdentity,
285 size: ThumbnailSize,
286 rendered_png: &[u8],
287 ) -> Result<InstalledThumbnailPngBytes> {
288 let (path, bytes) = self.install_thumbnail_entry(original, size, rendered_png)?;
289 Ok(InstalledThumbnailPngBytes { path, bytes })
290 }
291
292 pub fn install_raw_thumbnail_returning_path(
299 &self,
300 original: &ReadablePersonalOriginalIdentity,
301 size: ThumbnailSize,
302 image: RawThumbnailImage<'_>,
303 ) -> Result<InstalledThumbnailPath> {
304 let (path, _) = self.install_thumbnail_raw_entry(original, size, image)?;
305 Ok(InstalledThumbnailPath { path })
306 }
307
308 pub fn install_raw_thumbnail_returning_png_bytes(
315 &self,
316 original: &ReadablePersonalOriginalIdentity,
317 size: ThumbnailSize,
318 image: RawThumbnailImage<'_>,
319 ) -> Result<InstalledThumbnailPngBytes> {
320 let (path, bytes) = self.install_thumbnail_raw_entry(original, size, image)?;
321 Ok(InstalledThumbnailPngBytes { path, bytes })
322 }
323
324 pub fn materialize_thumbnail_from_larger_cache_returning_path(
330 &self,
331 original: &ReadablePersonalOriginalIdentity,
332 size: ThumbnailSize,
333 ) -> Result<PersonalThumbnailLookup<MaterializedThumbnailPath>> {
334 match self.materialize_thumbnail_from_larger_cache_entry(original, size)? {
335 PersonalThumbnailLookup::Valid(entry) => {
336 Ok(PersonalThumbnailLookup::Valid(MaterializedThumbnailPath {
337 target_path: entry.target_path,
338 source_path: entry.source_path,
339 requested_size: entry.requested_size,
340 source_size: entry.source_size,
341 written: entry.written,
342 }))
343 }
344 PersonalThumbnailLookup::Missing => Ok(PersonalThumbnailLookup::Missing),
345 PersonalThumbnailLookup::Invalid(problems) => {
346 Ok(PersonalThumbnailLookup::Invalid(problems))
347 }
348 }
349 }
350
351 pub fn materialize_thumbnail_from_larger_cache_returning_png_bytes(
357 &self,
358 original: &ReadablePersonalOriginalIdentity,
359 size: ThumbnailSize,
360 ) -> Result<PersonalThumbnailLookup<MaterializedThumbnailPngBytes>> {
361 match self.materialize_thumbnail_from_larger_cache_entry(original, size)? {
362 PersonalThumbnailLookup::Valid(entry) => Ok(PersonalThumbnailLookup::Valid(
363 MaterializedThumbnailPngBytes {
364 target_path: entry.target_path,
365 source_path: entry.source_path,
366 requested_size: entry.requested_size,
367 source_size: entry.source_size,
368 written: entry.written,
369 bytes: entry.bytes,
370 },
371 )),
372 PersonalThumbnailLookup::Missing => Ok(PersonalThumbnailLookup::Missing),
373 PersonalThumbnailLookup::Invalid(problems) => {
374 Ok(PersonalThumbnailLookup::Invalid(problems))
375 }
376 }
377 }
378
379 pub fn materialize_shared_thumbnail_returning_path(
388 &self,
389 shared: &SharedRepositoryContext,
390 original_facts: SharedOriginalFacts,
391 size: ThumbnailSize,
392 ) -> Result<SharedThumbnailLookup<MaterializedThumbnailPath>> {
393 match self.materialize_shared_thumbnail_entry(shared, original_facts, size)? {
394 SharedThumbnailLookup::FullyVerified(entry) => Ok(
395 SharedThumbnailLookup::FullyVerified(MaterializedThumbnailPath {
396 target_path: entry.target_path,
397 source_path: entry.source_path,
398 requested_size: entry.requested_size,
399 source_size: entry.source_size,
400 written: entry.written,
401 }),
402 ),
403 SharedThumbnailLookup::MetadataIncomplete(entry) => Ok(
404 SharedThumbnailLookup::MetadataIncomplete(MaterializedThumbnailPath {
405 target_path: entry.target_path,
406 source_path: entry.source_path,
407 requested_size: entry.requested_size,
408 source_size: entry.source_size,
409 written: entry.written,
410 }),
411 ),
412 SharedThumbnailLookup::Missing => Ok(SharedThumbnailLookup::Missing),
413 SharedThumbnailLookup::Invalid(problems) => {
414 Ok(SharedThumbnailLookup::Invalid(problems))
415 }
416 SharedThumbnailLookup::Unverifiable(problems) => {
417 Ok(SharedThumbnailLookup::Unverifiable(problems))
418 }
419 }
420 }
421
422 pub fn materialize_shared_thumbnail_returning_png_bytes(
431 &self,
432 shared: &SharedRepositoryContext,
433 original_facts: SharedOriginalFacts,
434 size: ThumbnailSize,
435 ) -> Result<SharedThumbnailLookup<MaterializedThumbnailPngBytes>> {
436 match self.materialize_shared_thumbnail_entry(shared, original_facts, size)? {
437 SharedThumbnailLookup::FullyVerified(entry) => Ok(
438 SharedThumbnailLookup::FullyVerified(MaterializedThumbnailPngBytes {
439 target_path: entry.target_path,
440 source_path: entry.source_path,
441 requested_size: entry.requested_size,
442 source_size: entry.source_size,
443 written: entry.written,
444 bytes: entry.bytes,
445 }),
446 ),
447 SharedThumbnailLookup::MetadataIncomplete(entry) => Ok(
448 SharedThumbnailLookup::MetadataIncomplete(MaterializedThumbnailPngBytes {
449 target_path: entry.target_path,
450 source_path: entry.source_path,
451 requested_size: entry.requested_size,
452 source_size: entry.source_size,
453 written: entry.written,
454 bytes: entry.bytes,
455 }),
456 ),
457 SharedThumbnailLookup::Missing => Ok(SharedThumbnailLookup::Missing),
458 SharedThumbnailLookup::Invalid(problems) => {
459 Ok(SharedThumbnailLookup::Invalid(problems))
460 }
461 SharedThumbnailLookup::Unverifiable(problems) => {
462 Ok(SharedThumbnailLookup::Unverifiable(problems))
463 }
464 }
465 }
466
467 fn install_thumbnail_entry(
468 &self,
469 original: &ReadablePersonalOriginalIdentity,
470 size: ThumbnailSize,
471 rendered_png: &[u8],
472 ) -> Result<(PathBuf, Vec<u8>)> {
473 let namespace = CacheNamespace::Size(size);
474 let path = self.cache_entry_path(original.identity().uri(), &namespace);
475 let bytes = normalized_personal_thumbnail_png(rendered_png, original.identity(), size)?;
476 self.write_personal_entry(&path, &namespace, &bytes)?;
477 Ok((path, bytes))
478 }
479
480 fn install_thumbnail_raw_entry(
481 &self,
482 original: &ReadablePersonalOriginalIdentity,
483 size: ThumbnailSize,
484 image: RawThumbnailImage<'_>,
485 ) -> Result<(PathBuf, Vec<u8>)> {
486 let namespace = CacheNamespace::Size(size);
487 let path = self.cache_entry_path(original.identity().uri(), &namespace);
488 let bytes = normalized_personal_thumbnail_raw_png(image, original.identity(), size)?;
489 self.write_personal_entry(&path, &namespace, &bytes)?;
490 Ok((path, bytes))
491 }
492
493 fn materialize_thumbnail_from_larger_cache_entry(
494 &self,
495 original: &ReadablePersonalOriginalIdentity,
496 size: ThumbnailSize,
497 ) -> Result<PersonalThumbnailLookup<MaterializedPersonalEntry>> {
498 let target_path =
499 self.cache_entry_path(original.identity().uri(), &CacheNamespace::Size(size));
500 for source_size in display_candidate_sizes(size) {
501 match self.lookup_thumbnail_entry(original, source_size)? {
502 PersonalThumbnailLookup::Valid(entry) if source_size == size => {
503 return Ok(PersonalThumbnailLookup::Valid(MaterializedPersonalEntry {
504 target_path,
505 source_path: entry.path,
506 requested_size: size,
507 source_size,
508 written: false,
509 bytes: entry.bytes,
510 }));
511 }
512 PersonalThumbnailLookup::Valid(entry) => {
513 let bytes = normalized_personal_thumbnail_from_cache_png(
514 &entry.bytes,
515 original.identity(),
516 size,
517 )?;
518 self.write_personal_entry(&target_path, &CacheNamespace::Size(size), &bytes)?;
519 return Ok(PersonalThumbnailLookup::Valid(MaterializedPersonalEntry {
520 target_path,
521 source_path: entry.path,
522 requested_size: size,
523 source_size,
524 written: true,
525 bytes,
526 }));
527 }
528 PersonalThumbnailLookup::Missing => {}
529 PersonalThumbnailLookup::Invalid(problems) => {
530 return Ok(PersonalThumbnailLookup::Invalid(problems));
531 }
532 }
533 }
534 Ok(PersonalThumbnailLookup::Missing)
535 }
536
537 fn materialize_shared_thumbnail_entry(
538 &self,
539 shared: &SharedRepositoryContext,
540 original_facts: SharedOriginalFacts,
541 size: ThumbnailSize,
542 ) -> Result<SharedThumbnailLookup<MaterializedPersonalEntry>> {
543 let original = personal_identity_from_shared_facts(shared, original_facts)?;
544 let target_path = self.cache_entry_path(original.uri(), &CacheNamespace::Size(size));
545 for source_size in display_candidate_sizes(size) {
546 match shared.lookup_thumbnail_entry(source_size, original_facts)? {
547 SharedThumbnailLookup::FullyVerified(entry) => {
548 let bytes = normalized_personal_thumbnail_from_cache_png(
549 &entry.bytes,
550 &original,
551 size,
552 )?;
553 self.write_personal_entry(&target_path, &CacheNamespace::Size(size), &bytes)?;
554 return Ok(SharedThumbnailLookup::FullyVerified(
555 MaterializedPersonalEntry {
556 target_path,
557 source_path: entry.path,
558 requested_size: size,
559 source_size,
560 written: true,
561 bytes,
562 },
563 ));
564 }
565 SharedThumbnailLookup::MetadataIncomplete(entry) => {
566 let bytes = normalized_personal_thumbnail_from_cache_png(
567 &entry.bytes,
568 &original,
569 size,
570 )?;
571 self.write_personal_entry(&target_path, &CacheNamespace::Size(size), &bytes)?;
572 return Ok(SharedThumbnailLookup::MetadataIncomplete(
573 MaterializedPersonalEntry {
574 target_path,
575 source_path: entry.path,
576 requested_size: size,
577 source_size,
578 written: true,
579 bytes,
580 },
581 ));
582 }
583 SharedThumbnailLookup::Missing => {}
584 SharedThumbnailLookup::Invalid(problems) => {
585 return Ok(SharedThumbnailLookup::Invalid(problems));
586 }
587 SharedThumbnailLookup::Unverifiable(problems) => {
588 return Ok(SharedThumbnailLookup::Unverifiable(problems));
589 }
590 }
591 }
592 Ok(SharedThumbnailLookup::Missing)
593 }
594
595 pub fn write_failure_entry_returning_path(
602 &self,
603 original: &ReadablePersonalOriginalIdentity,
604 namespace: &FailureNamespace,
605 ) -> Result<InstalledThumbnailPath> {
606 let (path, _) = self.write_failure_entry_returning_png_bytes_inner(original, namespace)?;
607 Ok(InstalledThumbnailPath { path })
608 }
609
610 pub fn write_failure_entry_returning_png_bytes(
617 &self,
618 original: &ReadablePersonalOriginalIdentity,
619 namespace: &FailureNamespace,
620 ) -> Result<InstalledThumbnailPngBytes> {
621 let (path, bytes) =
622 self.write_failure_entry_returning_png_bytes_inner(original, namespace)?;
623 Ok(InstalledThumbnailPngBytes { path, bytes })
624 }
625
626 fn write_failure_entry_returning_png_bytes_inner(
627 &self,
628 original: &ReadablePersonalOriginalIdentity,
629 namespace: &FailureNamespace,
630 ) -> Result<(PathBuf, Vec<u8>)> {
631 let namespace = CacheNamespace::Failure(namespace.clone());
632 let path = self.cache_entry_path(original.identity().uri(), &namespace);
633 let bytes = encode_rgba_png(
634 1,
635 1,
636 &[0, 0, 0, 0],
637 &thumbnail_metadata_pairs(original.identity()),
638 )?;
639 self.write_personal_entry(&path, &namespace, &bytes)?;
640 Ok((path, bytes))
641 }
642
643 fn lookup_thumbnail_entry(
644 &self,
645 original: &ReadablePersonalOriginalIdentity,
646 size: ThumbnailSize,
647 ) -> Result<PersonalThumbnailLookup<ValidatedPersonalEntry>> {
648 let path = self.cache_entry_path(original.identity().uri(), &CacheNamespace::Size(size));
649 let bytes = match read_cache_entry_no_follow(&path, "read thumbnail cache entry")? {
650 CacheEntryRead::Bytes(bytes) => bytes,
651 CacheEntryRead::Missing => return Ok(PersonalThumbnailLookup::Missing),
652 CacheEntryRead::Unreadable => {
653 return Ok(PersonalThumbnailLookup::Invalid(vec![
654 CacheEntryProblem::UnreadableEntry,
655 ]));
656 }
657 };
658
659 match validate_personal_thumbnail(&bytes, original, size) {
660 PersonalValidationOutcome::FullyVerified => {
661 let parsed = ParsedThumbnailPng::parse(&bytes)?;
662 Ok(PersonalThumbnailLookup::Valid(ValidatedPersonalEntry {
663 path,
664 bytes,
665 metadata: parsed.into_metadata(),
666 }))
667 }
668 PersonalValidationOutcome::Invalid(problems) => {
669 Ok(PersonalThumbnailLookup::Invalid(problems))
670 }
671 }
672 }
673
674 fn write_personal_entry(
675 &self,
676 path: &Path,
677 namespace: &CacheNamespace,
678 bytes: &[u8],
679 ) -> Result<()> {
680 self.ensure_namespace_dir(namespace)?;
681 let parent = path
682 .parent()
683 .ok_or_else(|| ThumbnailError::InvalidCachePath {
684 path: path.to_owned(),
685 problem: CachePathProblem::MissingParentDirectory,
686 })?;
687 let mut temp = tempfile::Builder::new()
688 .prefix(".xdg-thumbnail-")
689 .tempfile_in(parent)
690 .map_err(|source| {
691 ThumbnailError::io(
692 "create thumbnail temporary file",
693 Some(parent.to_owned()),
694 source,
695 )
696 })?;
697 temp.as_file_mut().write_all(bytes).map_err(|source| {
698 ThumbnailError::io(
699 "write thumbnail temporary file",
700 Some(temp.path().to_owned()),
701 source,
702 )
703 })?;
704 temp.as_file_mut()
705 .set_permissions(fs::Permissions::from_mode(0o600))
706 .map_err(|source| {
707 ThumbnailError::io(
708 "set thumbnail temporary file permissions",
709 Some(temp.path().to_owned()),
710 source,
711 )
712 })?;
713 temp.as_file_mut().sync_all().map_err(|source| {
714 ThumbnailError::io(
715 "sync thumbnail temporary file",
716 Some(temp.path().to_owned()),
717 source,
718 )
719 })?;
720 fs::rename(temp.path(), path).map_err(|source| {
721 ThumbnailError::io(
722 "publish thumbnail cache entry",
723 Some(path.to_owned()),
724 source,
725 )
726 })?;
727 Ok(())
728 }
729
730 fn ensure_namespace_dir(&self, namespace: &CacheNamespace) -> Result<()> {
731 ensure_private_directory(&self.path)?;
732 match namespace {
733 CacheNamespace::Size(size) => {
734 ensure_private_directory(&self.path.join(size.directory_name()))
735 }
736 CacheNamespace::Failure(namespace) => {
737 let fail = self.path.join("fail");
738 ensure_private_directory(&fail)?;
739 ensure_private_directory(&fail.join(namespace.as_str()))
740 }
741 }
742 }
743}
744
745impl AsRef<Path> for PersonalCacheRoot {
746 fn as_ref(&self) -> &Path {
747 self.as_path()
748 }
749}
750
751impl SharedRepositoryContext {
752 pub fn lookup_thumbnail_path(
759 &self,
760 original_facts: SharedOriginalFacts,
761 size: ThumbnailSize,
762 ) -> Result<SharedThumbnailLookup<ThumbnailPathLookupEntry>> {
763 match self.lookup_thumbnail_entry(size, original_facts)? {
764 SharedThumbnailLookup::FullyVerified(entry) => Ok(
765 SharedThumbnailLookup::FullyVerified(ThumbnailPathLookupEntry {
766 path: entry.path,
767 metadata: entry.metadata,
768 }),
769 ),
770 SharedThumbnailLookup::MetadataIncomplete(entry) => Ok(
771 SharedThumbnailLookup::MetadataIncomplete(ThumbnailPathLookupEntry {
772 path: entry.path,
773 metadata: entry.metadata,
774 }),
775 ),
776 SharedThumbnailLookup::Missing => Ok(SharedThumbnailLookup::Missing),
777 SharedThumbnailLookup::Invalid(problems) => {
778 Ok(SharedThumbnailLookup::Invalid(problems))
779 }
780 SharedThumbnailLookup::Unverifiable(problems) => {
781 Ok(SharedThumbnailLookup::Unverifiable(problems))
782 }
783 }
784 }
785
786 pub fn lookup_thumbnail_png_bytes(
793 &self,
794 original_facts: SharedOriginalFacts,
795 size: ThumbnailSize,
796 ) -> Result<SharedThumbnailLookup<ThumbnailPngBytesLookupEntry>> {
797 match self.lookup_thumbnail_entry(size, original_facts)? {
798 SharedThumbnailLookup::FullyVerified(entry) => Ok(
799 SharedThumbnailLookup::FullyVerified(ThumbnailPngBytesLookupEntry {
800 path: entry.path,
801 bytes: entry.bytes,
802 metadata: entry.metadata,
803 }),
804 ),
805 SharedThumbnailLookup::MetadataIncomplete(entry) => Ok(
806 SharedThumbnailLookup::MetadataIncomplete(ThumbnailPngBytesLookupEntry {
807 path: entry.path,
808 bytes: entry.bytes,
809 metadata: entry.metadata,
810 }),
811 ),
812 SharedThumbnailLookup::Missing => Ok(SharedThumbnailLookup::Missing),
813 SharedThumbnailLookup::Invalid(problems) => {
814 Ok(SharedThumbnailLookup::Invalid(problems))
815 }
816 SharedThumbnailLookup::Unverifiable(problems) => {
817 Ok(SharedThumbnailLookup::Unverifiable(problems))
818 }
819 }
820 }
821
822 pub fn lookup_thumbnail_rgba8(
832 &self,
833 original_facts: SharedOriginalFacts,
834 size: ThumbnailSize,
835 ) -> Result<SharedThumbnailLookup<ThumbnailRgba8LookupEntry>> {
836 match self.lookup_thumbnail_entry(size, original_facts)? {
837 SharedThumbnailLookup::FullyVerified(entry) => {
838 Ok(SharedThumbnailLookup::FullyVerified(
839 rgba8_lookup_entry_from_parts(entry.path, &entry.bytes, entry.metadata)?,
840 ))
841 }
842 SharedThumbnailLookup::MetadataIncomplete(entry) => {
843 Ok(SharedThumbnailLookup::MetadataIncomplete(
844 rgba8_lookup_entry_from_parts(entry.path, &entry.bytes, entry.metadata)?,
845 ))
846 }
847 SharedThumbnailLookup::Missing => Ok(SharedThumbnailLookup::Missing),
848 SharedThumbnailLookup::Invalid(problems) => {
849 Ok(SharedThumbnailLookup::Invalid(problems))
850 }
851 SharedThumbnailLookup::Unverifiable(problems) => {
852 Ok(SharedThumbnailLookup::Unverifiable(problems))
853 }
854 }
855 }
856
857 pub fn lookup_display_thumbnail_rgba8(
867 &self,
868 original_facts: SharedOriginalFacts,
869 size: ThumbnailSize,
870 ) -> Result<SharedThumbnailLookup<DisplayThumbnailRgba8LookupEntry>> {
871 for source_size in display_candidate_sizes(size) {
872 match self.lookup_thumbnail_entry(source_size, original_facts)? {
873 SharedThumbnailLookup::FullyVerified(entry) => {
874 return Ok(SharedThumbnailLookup::FullyVerified(
875 display_rgba8_lookup_entry_from_parts(
876 entry.path,
877 &entry.bytes,
878 entry.metadata,
879 size,
880 source_size,
881 )?,
882 ));
883 }
884 SharedThumbnailLookup::MetadataIncomplete(entry) => {
885 return Ok(SharedThumbnailLookup::MetadataIncomplete(
886 display_rgba8_lookup_entry_from_parts(
887 entry.path,
888 &entry.bytes,
889 entry.metadata,
890 size,
891 source_size,
892 )?,
893 ));
894 }
895 SharedThumbnailLookup::Missing => {}
896 SharedThumbnailLookup::Invalid(problems) => {
897 return Ok(SharedThumbnailLookup::Invalid(problems));
898 }
899 SharedThumbnailLookup::Unverifiable(problems) => {
900 return Ok(SharedThumbnailLookup::Unverifiable(problems));
901 }
902 }
903 }
904 Ok(SharedThumbnailLookup::Missing)
905 }
906
907 pub fn inspect_thumbnails(
914 &self,
915 sizes: &[ThumbnailSize],
916 original: SharedOriginalMetadata,
917 ) -> Result<Vec<SharedCacheEntryInspection>> {
918 let mut inspections = Vec::new();
919 for &size in sizes {
920 if let Some(inspection) = self.inspect_thumbnail(size, original)? {
921 inspections.push(inspection);
922 }
923 }
924 Ok(inspections)
925 }
926
927 fn lookup_thumbnail_entry(
928 &self,
929 size: ThumbnailSize,
930 original_facts: SharedOriginalFacts,
931 ) -> Result<SharedThumbnailLookup<ValidatedSharedEntry>> {
932 let path = self.cache_entry_path(size);
933 let bytes = match read_cache_entry_no_follow(&path, "read shared thumbnail cache entry")? {
934 CacheEntryRead::Bytes(bytes) => bytes,
935 CacheEntryRead::Missing => return Ok(SharedThumbnailLookup::Missing),
936 CacheEntryRead::Unreadable => {
937 return Ok(SharedThumbnailLookup::Invalid(vec![
938 CacheEntryProblem::UnreadableEntry,
939 ]));
940 }
941 };
942
943 match validate_shared_thumbnail(&bytes, self, original_facts.metadata(), size) {
944 SharedValidationOutcome::FullyVerified => {
945 let parsed = ParsedThumbnailPng::parse(&bytes)?;
946 Ok(SharedThumbnailLookup::FullyVerified(ValidatedSharedEntry {
947 path,
948 bytes,
949 metadata: parsed.into_metadata(),
950 }))
951 }
952 SharedValidationOutcome::MetadataIncomplete => {
953 if original_facts.metadata_policy()
954 == SharedThumbnailMetadataPolicy::RequireComplete
955 {
956 let parsed = ParsedThumbnailPng::parse(&bytes)?;
957 Ok(SharedThumbnailLookup::Invalid(
958 missing_required_shared_metadata_problems(parsed.metadata()),
959 ))
960 } else {
961 let parsed = ParsedThumbnailPng::parse(&bytes)?;
962 Ok(SharedThumbnailLookup::MetadataIncomplete(
963 ValidatedSharedEntry {
964 path,
965 bytes,
966 metadata: parsed.into_metadata(),
967 },
968 ))
969 }
970 }
971 SharedValidationOutcome::Invalid(problems) if only_unverifiable_original(&problems) => {
972 Ok(SharedThumbnailLookup::Unverifiable(problems))
973 }
974 SharedValidationOutcome::Invalid(problems) => {
975 Ok(SharedThumbnailLookup::Invalid(problems))
976 }
977 }
978 }
979
980 fn inspect_thumbnail(
981 &self,
982 size: ThumbnailSize,
983 original: SharedOriginalMetadata,
984 ) -> Result<Option<SharedCacheEntryInspection>> {
985 let path = self.cache_entry_path(size);
986 let metadata = match fs::symlink_metadata(&path) {
987 Ok(metadata) => metadata,
988 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
989 Err(_) => {
990 return Ok(Some(SharedCacheEntryInspection {
991 outcome: SharedCacheEntryOutcome::Invalid(vec![
992 CacheEntryProblem::UnreadableEntry,
993 ]),
994 shared_uri: self.shared_uri().clone(),
995 timestamps: thumbnail_timestamps(&path, AccessTimePreservation::NotNeeded),
996 size,
997 path,
998 metadata: None,
999 }));
1000 }
1001 };
1002
1003 if metadata.file_type().is_symlink() || !metadata.is_file() {
1004 return Ok(Some(SharedCacheEntryInspection {
1005 outcome: SharedCacheEntryOutcome::Invalid(vec![CacheEntryProblem::UnreadableEntry]),
1006 shared_uri: self.shared_uri().clone(),
1007 timestamps: thumbnail_timestamps_from_metadata(
1008 &metadata,
1009 AccessTimePreservation::NotNeeded,
1010 ),
1011 size,
1012 path,
1013 metadata: None,
1014 }));
1015 }
1016
1017 let (read_result, preservation) = read_thumbnail_for_inspection(&path);
1018 let timestamps = thumbnail_timestamps_from_metadata(&metadata, preservation);
1019 let bytes = match read_result {
1020 Ok(bytes) => bytes,
1021 Err(_) => {
1022 return Ok(Some(SharedCacheEntryInspection {
1023 outcome: SharedCacheEntryOutcome::Invalid(vec![
1024 CacheEntryProblem::UnreadableEntry,
1025 ]),
1026 shared_uri: self.shared_uri().clone(),
1027 timestamps,
1028 size,
1029 path,
1030 metadata: None,
1031 }));
1032 }
1033 };
1034
1035 let parsed = ParsedThumbnailPng::parse(&bytes).ok();
1036 let outcome =
1037 shared_cache_entry_outcome(validate_shared_thumbnail(&bytes, self, original, size));
1038 Ok(Some(SharedCacheEntryInspection {
1039 outcome,
1040 shared_uri: self.shared_uri().clone(),
1041 timestamps,
1042 size,
1043 path,
1044 metadata: parsed.map(ParsedThumbnailPng::into_metadata),
1045 }))
1046 }
1047}
1048
1049fn missing_required_shared_metadata_problems(
1050 metadata: &ThumbnailMetadata,
1051) -> Vec<CacheEntryProblem> {
1052 let mut problems = Vec::new();
1053 if metadata.thumb_uri().is_none() {
1054 push_problem(
1055 &mut problems,
1056 metadata_problem(
1057 ThumbnailMetadataKey::Uri,
1058 ThumbnailMetadataProblemKind::MissingRequired,
1059 ),
1060 );
1061 }
1062 if matches!(metadata.thumb_mtime_result(), Ok(None)) {
1063 push_problem(
1064 &mut problems,
1065 metadata_problem(
1066 ThumbnailMetadataKey::Mtime,
1067 ThumbnailMetadataProblemKind::MissingRequired,
1068 ),
1069 );
1070 }
1071 problems
1072}
1073
1074#[derive(Clone, Debug, Eq, PartialEq)]
1079pub struct PersonalThumbnailLookupRequest {
1080 root: PersonalCacheRoot,
1081 original: ReadablePersonalOriginalIdentity,
1082 size: ThumbnailSize,
1083}
1084
1085impl PersonalThumbnailLookupRequest {
1086 #[must_use]
1088 pub fn new(
1089 root: PersonalCacheRoot,
1090 original: ReadablePersonalOriginalIdentity,
1091 size: ThumbnailSize,
1092 ) -> Self {
1093 Self {
1094 root,
1095 original,
1096 size,
1097 }
1098 }
1099
1100 pub fn lookup_path(self) -> Result<PersonalThumbnailLookup<ThumbnailPathLookupEntry>> {
1106 let Self {
1107 root,
1108 original,
1109 size,
1110 } = self;
1111 root.lookup_thumbnail_path(&original, size)
1112 }
1113
1114 pub fn lookup_png_bytes(self) -> Result<PersonalThumbnailLookup<ThumbnailPngBytesLookupEntry>> {
1120 let Self {
1121 root,
1122 original,
1123 size,
1124 } = self;
1125 root.lookup_thumbnail_png_bytes(&original, size)
1126 }
1127
1128 pub fn lookup_rgba8(self) -> Result<PersonalThumbnailLookup<ThumbnailRgba8LookupEntry>> {
1134 let Self {
1135 root,
1136 original,
1137 size,
1138 } = self;
1139 root.lookup_thumbnail_rgba8(&original, size)
1140 }
1141
1142 pub fn lookup_display_rgba8(
1148 self,
1149 ) -> Result<PersonalThumbnailLookup<DisplayThumbnailRgba8LookupEntry>> {
1150 let Self {
1151 root,
1152 original,
1153 size,
1154 } = self;
1155 root.lookup_display_thumbnail_rgba8(&original, size)
1156 }
1157
1158 #[must_use]
1160 pub fn into_parts(self) -> PersonalThumbnailLookupRequestParts {
1161 PersonalThumbnailLookupRequestParts {
1162 root: self.root,
1163 original: self.original,
1164 size: self.size,
1165 }
1166 }
1167}
1168
1169#[derive(Clone, Debug, Eq, PartialEq)]
1171#[non_exhaustive]
1172pub struct PersonalThumbnailLookupRequestParts {
1173 pub root: PersonalCacheRoot,
1175 pub original: ReadablePersonalOriginalIdentity,
1177 pub size: ThumbnailSize,
1179}
1180
1181#[derive(Clone, Debug, Eq, PartialEq)]
1186pub struct PersonalThumbnailMaterializationRequest {
1187 root: PersonalCacheRoot,
1188 original: ReadablePersonalOriginalIdentity,
1189 size: ThumbnailSize,
1190}
1191
1192impl PersonalThumbnailMaterializationRequest {
1193 #[must_use]
1195 pub fn new(
1196 root: PersonalCacheRoot,
1197 original: ReadablePersonalOriginalIdentity,
1198 size: ThumbnailSize,
1199 ) -> Self {
1200 Self {
1201 root,
1202 original,
1203 size,
1204 }
1205 }
1206
1207 pub fn materialize_path(self) -> Result<PersonalThumbnailLookup<MaterializedThumbnailPath>> {
1214 let Self {
1215 root,
1216 original,
1217 size,
1218 } = self;
1219 root.materialize_thumbnail_from_larger_cache_returning_path(&original, size)
1220 }
1221
1222 pub fn materialize_png_bytes(
1229 self,
1230 ) -> Result<PersonalThumbnailLookup<MaterializedThumbnailPngBytes>> {
1231 let Self {
1232 root,
1233 original,
1234 size,
1235 } = self;
1236 root.materialize_thumbnail_from_larger_cache_returning_png_bytes(&original, size)
1237 }
1238
1239 #[must_use]
1241 pub fn into_parts(self) -> PersonalThumbnailMaterializationRequestParts {
1242 PersonalThumbnailMaterializationRequestParts {
1243 root: self.root,
1244 original: self.original,
1245 size: self.size,
1246 }
1247 }
1248}
1249
1250#[derive(Clone, Debug, Eq, PartialEq)]
1252#[non_exhaustive]
1253pub struct PersonalThumbnailMaterializationRequestParts {
1254 pub root: PersonalCacheRoot,
1256 pub original: ReadablePersonalOriginalIdentity,
1258 pub size: ThumbnailSize,
1260}
1261
1262#[derive(Debug, Eq, PartialEq)]
1306pub struct PersonalThumbnailInstallRequest {
1307 root: PersonalCacheRoot,
1308 original: ReadablePersonalOriginalIdentity,
1309 size: ThumbnailSize,
1310 rendered_png: Vec<u8>,
1311}
1312
1313impl PersonalThumbnailInstallRequest {
1314 #[must_use]
1316 pub fn new(
1317 root: PersonalCacheRoot,
1318 original: ReadablePersonalOriginalIdentity,
1319 size: ThumbnailSize,
1320 rendered_png: Vec<u8>,
1321 ) -> Self {
1322 Self {
1323 root,
1324 original,
1325 size,
1326 rendered_png,
1327 }
1328 }
1329
1330 pub fn install_path(self) -> Result<InstalledThumbnailPath> {
1336 let Self {
1337 root,
1338 original,
1339 size,
1340 rendered_png,
1341 } = self;
1342 root.install_thumbnail_returning_path(&original, size, &rendered_png)
1343 }
1344
1345 pub fn install_png_bytes(self) -> Result<InstalledThumbnailPngBytes> {
1351 let Self {
1352 root,
1353 original,
1354 size,
1355 rendered_png,
1356 } = self;
1357 root.install_thumbnail_returning_png_bytes(&original, size, &rendered_png)
1358 }
1359
1360 #[must_use]
1362 pub fn into_parts(self) -> PersonalThumbnailInstallRequestParts {
1363 PersonalThumbnailInstallRequestParts {
1364 root: self.root,
1365 original: self.original,
1366 size: self.size,
1367 rendered_png: self.rendered_png,
1368 }
1369 }
1370}
1371
1372#[derive(Debug, Eq, PartialEq)]
1374#[non_exhaustive]
1375pub struct PersonalThumbnailInstallRequestParts {
1376 pub root: PersonalCacheRoot,
1378 pub original: ReadablePersonalOriginalIdentity,
1380 pub size: ThumbnailSize,
1382 pub rendered_png: Vec<u8>,
1384}
1385
1386#[derive(Debug, Eq, PartialEq)]
1391pub struct PersonalThumbnailRawInstallRequest {
1392 root: PersonalCacheRoot,
1393 original: ReadablePersonalOriginalIdentity,
1394 size: ThumbnailSize,
1395 image: OwnedRawThumbnailImage,
1396}
1397
1398impl PersonalThumbnailRawInstallRequest {
1399 #[must_use]
1401 pub fn new(
1402 root: PersonalCacheRoot,
1403 original: ReadablePersonalOriginalIdentity,
1404 size: ThumbnailSize,
1405 image: OwnedRawThumbnailImage,
1406 ) -> Self {
1407 Self {
1408 root,
1409 original,
1410 size,
1411 image,
1412 }
1413 }
1414
1415 pub fn install_path(self) -> Result<InstalledThumbnailPath> {
1421 let Self {
1422 root,
1423 original,
1424 size,
1425 image,
1426 } = self;
1427 root.install_raw_thumbnail_returning_path(&original, size, image.as_borrowed())
1428 }
1429
1430 pub fn install_png_bytes(self) -> Result<InstalledThumbnailPngBytes> {
1436 let Self {
1437 root,
1438 original,
1439 size,
1440 image,
1441 } = self;
1442 root.install_raw_thumbnail_returning_png_bytes(&original, size, image.as_borrowed())
1443 }
1444
1445 #[must_use]
1447 pub fn into_parts(self) -> PersonalThumbnailRawInstallRequestParts {
1448 PersonalThumbnailRawInstallRequestParts {
1449 root: self.root,
1450 original: self.original,
1451 size: self.size,
1452 image: self.image,
1453 }
1454 }
1455}
1456
1457#[derive(Debug, Eq, PartialEq)]
1459#[non_exhaustive]
1460pub struct PersonalThumbnailRawInstallRequestParts {
1461 pub root: PersonalCacheRoot,
1463 pub original: ReadablePersonalOriginalIdentity,
1465 pub size: ThumbnailSize,
1467 pub image: OwnedRawThumbnailImage,
1469}
1470
1471#[derive(Clone, Debug, Eq, PartialEq)]
1476pub struct FailureEntryWriteRequest {
1477 root: PersonalCacheRoot,
1478 original: ReadablePersonalOriginalIdentity,
1479 namespace: FailureNamespace,
1480}
1481
1482impl FailureEntryWriteRequest {
1483 #[must_use]
1485 pub fn new(
1486 root: PersonalCacheRoot,
1487 original: ReadablePersonalOriginalIdentity,
1488 namespace: FailureNamespace,
1489 ) -> Self {
1490 Self {
1491 root,
1492 original,
1493 namespace,
1494 }
1495 }
1496
1497 pub fn write_path(self) -> Result<InstalledThumbnailPath> {
1503 let Self {
1504 root,
1505 original,
1506 namespace,
1507 } = self;
1508 root.write_failure_entry_returning_path(&original, &namespace)
1509 }
1510
1511 pub fn write_png_bytes(self) -> Result<InstalledThumbnailPngBytes> {
1517 let Self {
1518 root,
1519 original,
1520 namespace,
1521 } = self;
1522 root.write_failure_entry_returning_png_bytes(&original, &namespace)
1523 }
1524
1525 #[must_use]
1527 pub fn into_parts(self) -> FailureEntryWriteRequestParts {
1528 FailureEntryWriteRequestParts {
1529 root: self.root,
1530 original: self.original,
1531 namespace: self.namespace,
1532 }
1533 }
1534}
1535
1536#[derive(Clone, Debug, Eq, PartialEq)]
1538#[non_exhaustive]
1539pub struct FailureEntryWriteRequestParts {
1540 pub root: PersonalCacheRoot,
1542 pub original: ReadablePersonalOriginalIdentity,
1544 pub namespace: FailureNamespace,
1546}
1547
1548#[derive(Clone, Debug, Eq, PartialEq)]
1553pub struct PersonalThumbnailInspectionRequest {
1554 root: PersonalCacheRoot,
1555 sizes: Vec<ThumbnailSize>,
1556 nonstandard_entry_policy: NonstandardEntryPolicy,
1557}
1558
1559impl PersonalThumbnailInspectionRequest {
1560 #[must_use]
1562 pub fn new(
1563 root: PersonalCacheRoot,
1564 sizes: Vec<ThumbnailSize>,
1565 nonstandard_entry_policy: NonstandardEntryPolicy,
1566 ) -> Self {
1567 Self {
1568 root,
1569 sizes,
1570 nonstandard_entry_policy,
1571 }
1572 }
1573
1574 pub fn inspect(self) -> Result<Vec<CacheEntryInspection>> {
1580 let Self {
1581 root,
1582 sizes,
1583 nonstandard_entry_policy,
1584 } = self;
1585 root.inspect_thumbnails(&sizes, nonstandard_entry_policy)
1586 }
1587
1588 #[must_use]
1590 pub fn into_parts(self) -> PersonalThumbnailInspectionRequestParts {
1591 PersonalThumbnailInspectionRequestParts {
1592 root: self.root,
1593 sizes: self.sizes,
1594 nonstandard_entry_policy: self.nonstandard_entry_policy,
1595 }
1596 }
1597}
1598
1599#[derive(Clone, Debug, Eq, PartialEq)]
1601#[non_exhaustive]
1602pub struct PersonalThumbnailInspectionRequestParts {
1603 pub root: PersonalCacheRoot,
1605 pub sizes: Vec<ThumbnailSize>,
1607 pub nonstandard_entry_policy: NonstandardEntryPolicy,
1609}
1610
1611#[derive(Clone, Debug, Eq, PartialEq)]
1616pub struct FailureEntryInspectionRequest {
1617 root: PersonalCacheRoot,
1618 nonstandard_entry_policy: NonstandardEntryPolicy,
1619}
1620
1621impl FailureEntryInspectionRequest {
1622 #[must_use]
1624 pub fn new(root: PersonalCacheRoot, nonstandard_entry_policy: NonstandardEntryPolicy) -> Self {
1625 Self {
1626 root,
1627 nonstandard_entry_policy,
1628 }
1629 }
1630
1631 pub fn inspect(self) -> Result<Vec<CacheEntryInspection>> {
1637 let Self {
1638 root,
1639 nonstandard_entry_policy,
1640 } = self;
1641 root.inspect_failure_entries(nonstandard_entry_policy)
1642 }
1643
1644 #[must_use]
1646 pub fn into_parts(self) -> FailureEntryInspectionRequestParts {
1647 FailureEntryInspectionRequestParts {
1648 root: self.root,
1649 nonstandard_entry_policy: self.nonstandard_entry_policy,
1650 }
1651 }
1652}
1653
1654#[derive(Clone, Debug, Eq, PartialEq)]
1656#[non_exhaustive]
1657pub struct FailureEntryInspectionRequestParts {
1658 pub root: PersonalCacheRoot,
1660 pub nonstandard_entry_policy: NonstandardEntryPolicy,
1662}
1663
1664#[derive(Clone, Debug, Eq, PartialEq)]
1669pub struct SharedThumbnailLookupRequest {
1670 context: SharedRepositoryContext,
1671 original_facts: SharedOriginalFacts,
1672 size: ThumbnailSize,
1673}
1674
1675impl SharedThumbnailLookupRequest {
1676 #[must_use]
1678 pub fn new(
1679 context: SharedRepositoryContext,
1680 original_facts: SharedOriginalFacts,
1681 size: ThumbnailSize,
1682 ) -> Self {
1683 Self {
1684 context,
1685 original_facts,
1686 size,
1687 }
1688 }
1689
1690 pub fn lookup_path(self) -> Result<SharedThumbnailLookup<ThumbnailPathLookupEntry>> {
1696 let Self {
1697 context,
1698 original_facts,
1699 size,
1700 } = self;
1701 context.lookup_thumbnail_path(original_facts, size)
1702 }
1703
1704 pub fn lookup_png_bytes(self) -> Result<SharedThumbnailLookup<ThumbnailPngBytesLookupEntry>> {
1710 let Self {
1711 context,
1712 original_facts,
1713 size,
1714 } = self;
1715 context.lookup_thumbnail_png_bytes(original_facts, size)
1716 }
1717
1718 pub fn lookup_rgba8(self) -> Result<SharedThumbnailLookup<ThumbnailRgba8LookupEntry>> {
1724 let Self {
1725 context,
1726 original_facts,
1727 size,
1728 } = self;
1729 context.lookup_thumbnail_rgba8(original_facts, size)
1730 }
1731
1732 pub fn lookup_display_rgba8(
1738 self,
1739 ) -> Result<SharedThumbnailLookup<DisplayThumbnailRgba8LookupEntry>> {
1740 let Self {
1741 context,
1742 original_facts,
1743 size,
1744 } = self;
1745 context.lookup_display_thumbnail_rgba8(original_facts, size)
1746 }
1747
1748 #[must_use]
1750 pub fn into_parts(self) -> SharedThumbnailLookupRequestParts {
1751 SharedThumbnailLookupRequestParts {
1752 context: self.context,
1753 original_facts: self.original_facts,
1754 size: self.size,
1755 }
1756 }
1757}
1758
1759#[derive(Clone, Debug, Eq, PartialEq)]
1761#[non_exhaustive]
1762pub struct SharedThumbnailLookupRequestParts {
1763 pub context: SharedRepositoryContext,
1765 pub original_facts: SharedOriginalFacts,
1767 pub size: ThumbnailSize,
1769}
1770
1771#[derive(Clone, Debug, Eq, PartialEq)]
1776pub struct SharedToPersonalThumbnailMaterializationRequest {
1777 personal_root: PersonalCacheRoot,
1778 shared_context: SharedRepositoryContext,
1779 original_facts: SharedOriginalFacts,
1780 size: ThumbnailSize,
1781}
1782
1783impl SharedToPersonalThumbnailMaterializationRequest {
1784 #[must_use]
1786 pub fn new(
1787 personal_root: PersonalCacheRoot,
1788 shared_context: SharedRepositoryContext,
1789 original_facts: SharedOriginalFacts,
1790 size: ThumbnailSize,
1791 ) -> Self {
1792 Self {
1793 personal_root,
1794 shared_context,
1795 original_facts,
1796 size,
1797 }
1798 }
1799
1800 pub fn materialize_path(self) -> Result<SharedThumbnailLookup<MaterializedThumbnailPath>> {
1807 let Self {
1808 personal_root,
1809 shared_context,
1810 original_facts,
1811 size,
1812 } = self;
1813 personal_root.materialize_shared_thumbnail_returning_path(
1814 &shared_context,
1815 original_facts,
1816 size,
1817 )
1818 }
1819
1820 pub fn materialize_png_bytes(
1827 self,
1828 ) -> Result<SharedThumbnailLookup<MaterializedThumbnailPngBytes>> {
1829 let Self {
1830 personal_root,
1831 shared_context,
1832 original_facts,
1833 size,
1834 } = self;
1835 personal_root.materialize_shared_thumbnail_returning_png_bytes(
1836 &shared_context,
1837 original_facts,
1838 size,
1839 )
1840 }
1841
1842 #[must_use]
1844 pub fn into_parts(self) -> SharedToPersonalThumbnailMaterializationRequestParts {
1845 SharedToPersonalThumbnailMaterializationRequestParts {
1846 personal_root: self.personal_root,
1847 shared_context: self.shared_context,
1848 original_facts: self.original_facts,
1849 size: self.size,
1850 }
1851 }
1852}
1853
1854#[derive(Clone, Debug, Eq, PartialEq)]
1856#[non_exhaustive]
1857pub struct SharedToPersonalThumbnailMaterializationRequestParts {
1858 pub personal_root: PersonalCacheRoot,
1860 pub shared_context: SharedRepositoryContext,
1862 pub original_facts: SharedOriginalFacts,
1864 pub size: ThumbnailSize,
1866}
1867
1868#[derive(Clone, Debug, Eq, PartialEq)]
1873pub struct SharedThumbnailInspectionRequest {
1874 context: SharedRepositoryContext,
1875 sizes: Vec<ThumbnailSize>,
1876 original: SharedOriginalMetadata,
1877}
1878
1879impl SharedThumbnailInspectionRequest {
1880 #[must_use]
1882 pub fn new(
1883 context: SharedRepositoryContext,
1884 sizes: Vec<ThumbnailSize>,
1885 original: SharedOriginalMetadata,
1886 ) -> Self {
1887 Self {
1888 context,
1889 sizes,
1890 original,
1891 }
1892 }
1893
1894 pub fn inspect(self) -> Result<Vec<SharedCacheEntryInspection>> {
1900 let Self {
1901 context,
1902 sizes,
1903 original,
1904 } = self;
1905 context.inspect_thumbnails(&sizes, original)
1906 }
1907
1908 #[must_use]
1910 pub fn into_parts(self) -> SharedThumbnailInspectionRequestParts {
1911 SharedThumbnailInspectionRequestParts {
1912 context: self.context,
1913 sizes: self.sizes,
1914 original: self.original,
1915 }
1916 }
1917}
1918
1919#[derive(Clone, Debug, Eq, PartialEq)]
1921#[non_exhaustive]
1922pub struct SharedThumbnailInspectionRequestParts {
1923 pub context: SharedRepositoryContext,
1925 pub sizes: Vec<ThumbnailSize>,
1927 pub original: SharedOriginalMetadata,
1929}
1930
1931fn only_unverifiable_original(problems: &[CacheEntryProblem]) -> bool {
1932 !problems.is_empty()
1933 && problems
1934 .iter()
1935 .all(|problem| *problem == CacheEntryProblem::UnverifiableOriginal)
1936}
1937
1938enum CacheEntryRead {
1939 Missing,
1940 Unreadable,
1941 Bytes(Vec<u8>),
1942}
1943
1944fn read_cache_entry_no_follow(path: &Path, context: &'static str) -> Result<CacheEntryRead> {
1945 let metadata = match fs::symlink_metadata(path) {
1946 Ok(metadata) => metadata,
1947 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
1948 return Ok(CacheEntryRead::Missing);
1949 }
1950 Err(source) => {
1951 return Err(ThumbnailError::io(context, Some(path.to_owned()), source));
1952 }
1953 };
1954 if metadata.file_type().is_symlink() || !metadata.is_file() {
1955 return Ok(CacheEntryRead::Unreadable);
1956 }
1957
1958 let flags = rustix::fs::OFlags::RDONLY
1959 | rustix::fs::OFlags::CLOEXEC
1960 | rustix::fs::OFlags::NOFOLLOW
1961 | rustix::fs::OFlags::NONBLOCK;
1962 let fd = match rustix::fs::open(path, flags, rustix::fs::Mode::empty()) {
1963 Ok(fd) => fd,
1964 Err(rustix::io::Errno::NOENT) => return Ok(CacheEntryRead::Missing),
1965 Err(rustix::io::Errno::LOOP | rustix::io::Errno::ISDIR | rustix::io::Errno::NOTDIR) => {
1966 return Ok(CacheEntryRead::Unreadable);
1967 }
1968 Err(rustix::io::Errno::ACCESS | rustix::io::Errno::PERM) => {
1969 return Ok(CacheEntryRead::Unreadable);
1970 }
1971 Err(source) => {
1972 return Err(ThumbnailError::io(
1973 context,
1974 Some(path.to_owned()),
1975 std::io::Error::from(source),
1976 ));
1977 }
1978 };
1979
1980 let stat = rustix::fs::fstat(&fd).map_err(|source| {
1981 ThumbnailError::io(context, Some(path.to_owned()), std::io::Error::from(source))
1982 })?;
1983 let file_type = rustix::fs::FileType::from_raw_mode(stat.st_mode);
1984 if !file_type.is_file() {
1985 return Ok(CacheEntryRead::Unreadable);
1986 }
1987
1988 let mut file = File::from(fd);
1989 let mut bytes = Vec::new();
1990 if let Err(source) = file.read_to_end(&mut bytes) {
1991 if source.kind() == std::io::ErrorKind::PermissionDenied {
1992 return Ok(CacheEntryRead::Unreadable);
1993 }
1994 return Err(ThumbnailError::io(context, Some(path.to_owned()), source));
1995 }
1996 Ok(CacheEntryRead::Bytes(bytes))
1997}
1998
1999fn shared_cache_entry_outcome(outcome: SharedValidationOutcome) -> SharedCacheEntryOutcome {
2000 match outcome {
2001 SharedValidationOutcome::FullyVerified => SharedCacheEntryOutcome::FullyVerified,
2002 SharedValidationOutcome::MetadataIncomplete => SharedCacheEntryOutcome::MetadataIncomplete,
2003 SharedValidationOutcome::Invalid(problems) if only_unverifiable_original(&problems) => {
2004 SharedCacheEntryOutcome::Unverifiable(problems)
2005 }
2006 SharedValidationOutcome::Invalid(problems) => SharedCacheEntryOutcome::Invalid(problems),
2007 }
2008}
2009
2010struct ValidatedPersonalEntry {
2011 path: PathBuf,
2012 bytes: Vec<u8>,
2013 metadata: ThumbnailMetadata,
2014}
2015
2016struct ValidatedSharedEntry {
2017 path: PathBuf,
2018 bytes: Vec<u8>,
2019 metadata: ThumbnailMetadata,
2020}
2021
2022struct MaterializedPersonalEntry {
2023 target_path: PathBuf,
2024 source_path: PathBuf,
2025 requested_size: ThumbnailSize,
2026 source_size: ThumbnailSize,
2027 written: bool,
2028 bytes: Vec<u8>,
2029}
2030
2031fn display_candidate_sizes(requested_size: ThumbnailSize) -> impl Iterator<Item = ThumbnailSize> {
2032 ThumbnailSize::all()
2033 .iter()
2034 .copied()
2035 .skip_while(move |size| *size != requested_size)
2036}
2037
2038fn personal_identity_from_shared_facts(
2039 shared: &SharedRepositoryContext,
2040 original_facts: SharedOriginalFacts,
2041) -> Result<PersonalOriginalIdentity> {
2042 let Some(mtime) = original_facts.mtime() else {
2043 return Err(ThumbnailError::invalid_metadata(
2044 "shared original mtime is required for personal materialization",
2045 ));
2046 };
2047 let original_path = shared.repository_root().join(shared.original_child_name());
2048 let uri = PersonalOriginalUri::from_absolute_path_bytes(original_path.as_os_str().as_bytes())?;
2049 let mut original = PersonalOriginalIdentity::new(uri, mtime);
2050 if let Some(size) = original_facts.original_byte_size() {
2051 original = original.with_original_byte_size(size);
2052 }
2053 Ok(original)
2054}
2055
2056fn rgba8_lookup_entry_from_parts(
2057 path: PathBuf,
2058 bytes: &[u8],
2059 metadata: ThumbnailMetadata,
2060) -> Result<ThumbnailRgba8LookupEntry> {
2061 let decoded = decode_validated_thumbnail_png_to_rgba8(bytes)?;
2062 Ok(ThumbnailRgba8LookupEntry {
2063 path,
2064 width: decoded.width,
2065 height: decoded.height,
2066 stride: decoded.stride,
2067 pixels: decoded.pixels,
2068 metadata,
2069 })
2070}
2071
2072fn display_rgba8_lookup_entry_from_parts(
2073 source_path: PathBuf,
2074 bytes: &[u8],
2075 source_metadata: ThumbnailMetadata,
2076 requested_size: ThumbnailSize,
2077 source_size: ThumbnailSize,
2078) -> Result<DisplayThumbnailRgba8LookupEntry> {
2079 let decoded = downscaled_validated_thumbnail_png_to_rgba8(bytes, requested_size)?;
2080 Ok(DisplayThumbnailRgba8LookupEntry {
2081 source_path,
2082 requested_size,
2083 source_size,
2084 width: decoded.width,
2085 height: decoded.height,
2086 stride: decoded.stride,
2087 pixels: decoded.pixels,
2088 source_metadata,
2089 })
2090}
2091
2092#[derive(Clone, Debug, Eq, PartialEq)]
2094pub enum PersonalThumbnailLookup<T> {
2095 Valid(T),
2097 Missing,
2099 Invalid(Vec<CacheEntryProblem>),
2101}
2102
2103#[derive(Clone, Debug, Eq, PartialEq)]
2105#[non_exhaustive]
2106pub enum SharedThumbnailLookup<T> {
2107 FullyVerified(T),
2109 MetadataIncomplete(T),
2111 Missing,
2113 Invalid(Vec<CacheEntryProblem>),
2115 Unverifiable(Vec<CacheEntryProblem>),
2117}
2118
2119#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2121#[non_exhaustive]
2122pub enum SharedThumbnailMetadataPolicy {
2123 RequireComplete,
2125 AllowIncomplete,
2127}
2128
2129#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
2131pub struct SharedOriginalMetadata {
2132 mtime: Option<UnixMtimeSeconds>,
2133 original_byte_size: Option<u64>,
2134}
2135
2136impl SharedOriginalMetadata {
2137 #[must_use]
2139 pub const fn new() -> Self {
2140 Self {
2141 mtime: None,
2142 original_byte_size: None,
2143 }
2144 }
2145
2146 #[must_use]
2148 pub const fn with_mtime(mut self, mtime: UnixMtimeSeconds) -> Self {
2149 self.mtime = Some(mtime);
2150 self
2151 }
2152
2153 #[must_use]
2155 pub const fn with_original_byte_size(mut self, original_byte_size: u64) -> Self {
2156 self.original_byte_size = Some(original_byte_size);
2157 self
2158 }
2159
2160 #[must_use]
2162 pub const fn mtime(&self) -> Option<UnixMtimeSeconds> {
2163 self.mtime
2164 }
2165
2166 #[must_use]
2168 pub const fn original_byte_size(&self) -> Option<u64> {
2169 self.original_byte_size
2170 }
2171}
2172
2173#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2175pub struct SharedOriginalFacts {
2176 metadata_policy: SharedThumbnailMetadataPolicy,
2177 metadata: SharedOriginalMetadata,
2178}
2179
2180impl SharedOriginalFacts {
2181 #[must_use]
2183 pub const fn new(
2184 metadata_policy: SharedThumbnailMetadataPolicy,
2185 metadata: SharedOriginalMetadata,
2186 ) -> Self {
2187 Self {
2188 metadata_policy,
2189 metadata,
2190 }
2191 }
2192
2193 #[must_use]
2195 pub const fn metadata_policy(&self) -> SharedThumbnailMetadataPolicy {
2196 self.metadata_policy
2197 }
2198
2199 #[must_use]
2201 pub const fn mtime(&self) -> Option<UnixMtimeSeconds> {
2202 self.metadata.mtime()
2203 }
2204
2205 #[must_use]
2207 pub const fn original_byte_size(&self) -> Option<u64> {
2208 self.metadata.original_byte_size()
2209 }
2210
2211 #[must_use]
2213 pub const fn metadata(&self) -> SharedOriginalMetadata {
2214 self.metadata
2215 }
2216}
2217
2218#[derive(Clone, Debug, Eq, PartialEq)]
2220#[non_exhaustive]
2221pub enum SharedCacheEntryOutcome {
2222 FullyVerified,
2224 MetadataIncomplete,
2226 Invalid(Vec<CacheEntryProblem>),
2228 Unverifiable(Vec<CacheEntryProblem>),
2230}
2231
2232#[derive(Clone, Debug, Eq, PartialEq)]
2234pub struct SharedCacheEntryInspection {
2235 outcome: SharedCacheEntryOutcome,
2236 shared_uri: SharedRelativeOriginalUri,
2237 timestamps: ThumbnailTimestamps,
2238 size: ThumbnailSize,
2239 path: PathBuf,
2240 metadata: Option<ThumbnailMetadata>,
2241}
2242
2243impl SharedCacheEntryInspection {
2244 #[must_use]
2246 pub const fn outcome(&self) -> &SharedCacheEntryOutcome {
2247 &self.outcome
2248 }
2249
2250 #[must_use]
2252 pub const fn shared_uri(&self) -> &SharedRelativeOriginalUri {
2253 &self.shared_uri
2254 }
2255
2256 #[must_use]
2258 pub const fn timestamps(&self) -> &ThumbnailTimestamps {
2259 &self.timestamps
2260 }
2261
2262 #[must_use]
2264 pub const fn size(&self) -> ThumbnailSize {
2265 self.size
2266 }
2267
2268 #[must_use]
2270 pub fn path(&self) -> &Path {
2271 &self.path
2272 }
2273
2274 #[must_use]
2276 pub const fn metadata(&self) -> Option<&ThumbnailMetadata> {
2277 self.metadata.as_ref()
2278 }
2279
2280 #[must_use]
2282 pub fn into_parts(self) -> SharedCacheEntryInspectionParts {
2283 SharedCacheEntryInspectionParts {
2284 outcome: self.outcome,
2285 shared_uri: self.shared_uri,
2286 timestamps: self.timestamps,
2287 size: self.size,
2288 path: self.path,
2289 metadata: self.metadata,
2290 }
2291 }
2292}
2293
2294#[derive(Clone, Debug, Eq, PartialEq)]
2296#[non_exhaustive]
2297pub struct SharedCacheEntryInspectionParts {
2298 pub outcome: SharedCacheEntryOutcome,
2300 pub shared_uri: SharedRelativeOriginalUri,
2302 pub timestamps: ThumbnailTimestamps,
2304 pub size: ThumbnailSize,
2306 pub path: PathBuf,
2308 pub metadata: Option<ThumbnailMetadata>,
2310}
2311
2312#[derive(Clone, Debug, Eq, PartialEq)]
2314pub struct ThumbnailPathLookupEntry {
2315 path: PathBuf,
2316 metadata: ThumbnailMetadata,
2317}
2318
2319impl ThumbnailPathLookupEntry {
2320 #[must_use]
2322 pub fn path(&self) -> &Path {
2323 &self.path
2324 }
2325
2326 #[must_use]
2328 pub const fn metadata(&self) -> &ThumbnailMetadata {
2329 &self.metadata
2330 }
2331
2332 #[must_use]
2334 pub fn into_parts(self) -> ThumbnailPathLookupEntryParts {
2335 ThumbnailPathLookupEntryParts {
2336 path: self.path,
2337 metadata: self.metadata,
2338 }
2339 }
2340}
2341
2342#[derive(Clone, Debug, Eq, PartialEq)]
2344#[non_exhaustive]
2345pub struct ThumbnailPathLookupEntryParts {
2346 pub path: PathBuf,
2348 pub metadata: ThumbnailMetadata,
2350}
2351
2352#[derive(Debug, Eq, PartialEq)]
2354pub struct ThumbnailPngBytesLookupEntry {
2355 path: PathBuf,
2356 bytes: Vec<u8>,
2357 metadata: ThumbnailMetadata,
2358}
2359
2360impl ThumbnailPngBytesLookupEntry {
2361 #[must_use]
2363 pub fn path(&self) -> &Path {
2364 &self.path
2365 }
2366
2367 #[must_use]
2369 pub fn png_bytes(&self) -> &[u8] {
2370 &self.bytes
2371 }
2372
2373 #[must_use]
2375 pub const fn metadata(&self) -> &ThumbnailMetadata {
2376 &self.metadata
2377 }
2378
2379 #[must_use]
2381 pub fn into_parts(self) -> ThumbnailPngBytesLookupEntryParts {
2382 ThumbnailPngBytesLookupEntryParts {
2383 path: self.path,
2384 png_bytes: self.bytes,
2385 metadata: self.metadata,
2386 }
2387 }
2388}
2389
2390#[derive(Debug, Eq, PartialEq)]
2392#[non_exhaustive]
2393pub struct ThumbnailPngBytesLookupEntryParts {
2394 pub path: PathBuf,
2396 pub png_bytes: Vec<u8>,
2398 pub metadata: ThumbnailMetadata,
2400}
2401
2402#[derive(Debug, Eq, PartialEq)]
2407pub struct ThumbnailRgba8LookupEntry {
2408 path: PathBuf,
2409 width: u32,
2410 height: u32,
2411 stride: usize,
2412 pixels: Vec<u8>,
2413 metadata: ThumbnailMetadata,
2414}
2415
2416impl ThumbnailRgba8LookupEntry {
2417 #[must_use]
2419 pub fn path(&self) -> &Path {
2420 &self.path
2421 }
2422
2423 #[must_use]
2425 pub const fn width(&self) -> u32 {
2426 self.width
2427 }
2428
2429 #[must_use]
2431 pub const fn height(&self) -> u32 {
2432 self.height
2433 }
2434
2435 #[must_use]
2439 pub const fn stride(&self) -> usize {
2440 self.stride
2441 }
2442
2443 #[must_use]
2445 pub fn pixels(&self) -> &[u8] {
2446 &self.pixels
2447 }
2448
2449 #[must_use]
2451 pub const fn metadata(&self) -> &ThumbnailMetadata {
2452 &self.metadata
2453 }
2454
2455 #[must_use]
2457 pub fn into_parts(self) -> ThumbnailRgba8LookupEntryParts {
2458 ThumbnailRgba8LookupEntryParts {
2459 path: self.path,
2460 width: self.width,
2461 height: self.height,
2462 stride: self.stride,
2463 pixels: self.pixels,
2464 metadata: self.metadata,
2465 }
2466 }
2467}
2468
2469#[derive(Debug, Eq, PartialEq)]
2471#[non_exhaustive]
2472pub struct ThumbnailRgba8LookupEntryParts {
2473 pub path: PathBuf,
2475 pub width: u32,
2477 pub height: u32,
2479 pub stride: usize,
2481 pub pixels: Vec<u8>,
2483 pub metadata: ThumbnailMetadata,
2485}
2486
2487#[derive(Debug, Eq, PartialEq)]
2493pub struct DisplayThumbnailRgba8LookupEntry {
2494 source_path: PathBuf,
2495 requested_size: ThumbnailSize,
2496 source_size: ThumbnailSize,
2497 width: u32,
2498 height: u32,
2499 stride: usize,
2500 pixels: Vec<u8>,
2501 source_metadata: ThumbnailMetadata,
2502}
2503
2504impl DisplayThumbnailRgba8LookupEntry {
2505 #[must_use]
2507 pub fn source_path(&self) -> &Path {
2508 &self.source_path
2509 }
2510
2511 #[must_use]
2513 pub const fn requested_size(&self) -> ThumbnailSize {
2514 self.requested_size
2515 }
2516
2517 #[must_use]
2519 pub const fn source_size(&self) -> ThumbnailSize {
2520 self.source_size
2521 }
2522
2523 #[must_use]
2525 pub const fn is_derived(&self) -> bool {
2526 self.requested_size as u8 != self.source_size as u8
2527 }
2528
2529 #[must_use]
2531 pub const fn width(&self) -> u32 {
2532 self.width
2533 }
2534
2535 #[must_use]
2537 pub const fn height(&self) -> u32 {
2538 self.height
2539 }
2540
2541 #[must_use]
2543 pub const fn stride(&self) -> usize {
2544 self.stride
2545 }
2546
2547 #[must_use]
2549 pub fn pixels(&self) -> &[u8] {
2550 &self.pixels
2551 }
2552
2553 #[must_use]
2555 pub const fn source_metadata(&self) -> &ThumbnailMetadata {
2556 &self.source_metadata
2557 }
2558
2559 #[must_use]
2561 pub fn into_parts(self) -> DisplayThumbnailRgba8LookupEntryParts {
2562 DisplayThumbnailRgba8LookupEntryParts {
2563 source_path: self.source_path,
2564 requested_size: self.requested_size,
2565 source_size: self.source_size,
2566 width: self.width,
2567 height: self.height,
2568 stride: self.stride,
2569 pixels: self.pixels,
2570 source_metadata: self.source_metadata,
2571 }
2572 }
2573}
2574
2575#[derive(Debug, Eq, PartialEq)]
2577#[non_exhaustive]
2578pub struct DisplayThumbnailRgba8LookupEntryParts {
2579 pub source_path: PathBuf,
2581 pub requested_size: ThumbnailSize,
2583 pub source_size: ThumbnailSize,
2585 pub width: u32,
2587 pub height: u32,
2589 pub stride: usize,
2591 pub pixels: Vec<u8>,
2593 pub source_metadata: ThumbnailMetadata,
2595}
2596
2597#[derive(Clone, Debug, Eq, PartialEq)]
2599pub struct InstalledThumbnailPath {
2600 path: PathBuf,
2601}
2602
2603impl InstalledThumbnailPath {
2604 #[must_use]
2606 pub fn path(&self) -> &Path {
2607 &self.path
2608 }
2609
2610 #[must_use]
2612 pub fn into_path_buf(self) -> PathBuf {
2613 self.path
2614 }
2615}
2616
2617impl AsRef<Path> for InstalledThumbnailPath {
2618 fn as_ref(&self) -> &Path {
2619 self.path()
2620 }
2621}
2622
2623#[derive(Clone, Debug, Eq, PartialEq)]
2625pub struct MaterializedThumbnailPath {
2626 target_path: PathBuf,
2627 source_path: PathBuf,
2628 requested_size: ThumbnailSize,
2629 source_size: ThumbnailSize,
2630 written: bool,
2631}
2632
2633impl MaterializedThumbnailPath {
2634 #[must_use]
2636 pub fn target_path(&self) -> &Path {
2637 &self.target_path
2638 }
2639
2640 #[must_use]
2642 pub fn source_path(&self) -> &Path {
2643 &self.source_path
2644 }
2645
2646 #[must_use]
2648 pub const fn requested_size(&self) -> ThumbnailSize {
2649 self.requested_size
2650 }
2651
2652 #[must_use]
2654 pub const fn source_size(&self) -> ThumbnailSize {
2655 self.source_size
2656 }
2657
2658 #[must_use]
2660 pub const fn written(&self) -> bool {
2661 self.written
2662 }
2663
2664 #[must_use]
2666 pub fn into_parts(self) -> MaterializedThumbnailPathParts {
2667 MaterializedThumbnailPathParts {
2668 target_path: self.target_path,
2669 source_path: self.source_path,
2670 requested_size: self.requested_size,
2671 source_size: self.source_size,
2672 written: self.written,
2673 }
2674 }
2675}
2676
2677#[derive(Clone, Debug, Eq, PartialEq)]
2679#[non_exhaustive]
2680pub struct MaterializedThumbnailPathParts {
2681 pub target_path: PathBuf,
2683 pub source_path: PathBuf,
2685 pub requested_size: ThumbnailSize,
2687 pub source_size: ThumbnailSize,
2689 pub written: bool,
2691}
2692
2693#[derive(Debug, Eq, PartialEq)]
2697pub struct MaterializedThumbnailPngBytes {
2698 target_path: PathBuf,
2699 source_path: PathBuf,
2700 requested_size: ThumbnailSize,
2701 source_size: ThumbnailSize,
2702 written: bool,
2703 bytes: Vec<u8>,
2704}
2705
2706impl MaterializedThumbnailPngBytes {
2707 #[must_use]
2709 pub fn target_path(&self) -> &Path {
2710 &self.target_path
2711 }
2712
2713 #[must_use]
2715 pub fn source_path(&self) -> &Path {
2716 &self.source_path
2717 }
2718
2719 #[must_use]
2721 pub const fn requested_size(&self) -> ThumbnailSize {
2722 self.requested_size
2723 }
2724
2725 #[must_use]
2727 pub const fn source_size(&self) -> ThumbnailSize {
2728 self.source_size
2729 }
2730
2731 #[must_use]
2733 pub const fn written(&self) -> bool {
2734 self.written
2735 }
2736
2737 #[must_use]
2739 pub fn png_bytes(&self) -> &[u8] {
2740 &self.bytes
2741 }
2742
2743 #[must_use]
2745 pub fn into_parts(self) -> MaterializedThumbnailPngBytesParts {
2746 MaterializedThumbnailPngBytesParts {
2747 target_path: self.target_path,
2748 source_path: self.source_path,
2749 requested_size: self.requested_size,
2750 source_size: self.source_size,
2751 written: self.written,
2752 png_bytes: self.bytes,
2753 }
2754 }
2755}
2756
2757#[derive(Debug, Eq, PartialEq)]
2759#[non_exhaustive]
2760pub struct MaterializedThumbnailPngBytesParts {
2761 pub target_path: PathBuf,
2763 pub source_path: PathBuf,
2765 pub requested_size: ThumbnailSize,
2767 pub source_size: ThumbnailSize,
2769 pub written: bool,
2771 pub png_bytes: Vec<u8>,
2773}
2774
2775#[derive(Debug, Eq, PartialEq)]
2781pub struct InstalledThumbnailPngBytes {
2782 path: PathBuf,
2783 bytes: Vec<u8>,
2784}
2785
2786impl InstalledThumbnailPngBytes {
2787 #[must_use]
2789 pub fn path(&self) -> &Path {
2790 &self.path
2791 }
2792
2793 #[must_use]
2795 pub fn png_bytes(&self) -> &[u8] {
2796 &self.bytes
2797 }
2798
2799 #[must_use]
2801 pub fn into_parts(self) -> InstalledThumbnailPngBytesParts {
2802 InstalledThumbnailPngBytesParts {
2803 path: self.path,
2804 png_bytes: self.bytes,
2805 }
2806 }
2807}
2808
2809#[derive(Debug, Eq, PartialEq)]
2811#[non_exhaustive]
2812pub struct InstalledThumbnailPngBytesParts {
2813 pub path: PathBuf,
2815 pub png_bytes: Vec<u8>,
2817}
2818
2819fn ensure_private_directory(path: &Path) -> Result<()> {
2820 match fs::symlink_metadata(path) {
2821 Ok(metadata) => {
2822 let problem = if metadata.file_type().is_symlink() {
2823 Some(CacheDirectoryProblem::Symlink)
2824 } else if !metadata.is_dir() {
2825 Some(CacheDirectoryProblem::NotDirectory)
2826 } else if metadata.uid() != rustix::process::getuid().as_raw() {
2827 Some(CacheDirectoryProblem::WrongOwner)
2828 } else if metadata.permissions().mode() & 0o077 != 0 {
2829 Some(CacheDirectoryProblem::GroupOrOtherAccessible)
2830 } else {
2831 None
2832 };
2833 if let Some(problem) = problem {
2834 return Err(ThumbnailError::InsecureCacheDirectory {
2835 path: path.to_owned(),
2836 problem,
2837 });
2838 }
2839 Ok(())
2840 }
2841 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
2842 if let Some(parent) = path.parent() {
2843 fs::create_dir_all(parent).map_err(|source| {
2844 ThumbnailError::io(
2845 "create parent thumbnail cache directories",
2846 Some(parent.to_owned()),
2847 source,
2848 )
2849 })?;
2850 }
2851 match fs::DirBuilder::new().mode(0o700).create(path) {
2852 Ok(()) => {}
2853 Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
2854 return ensure_private_directory(path);
2855 }
2856 Err(source) => {
2857 return Err(ThumbnailError::io(
2858 "create thumbnail cache directory",
2859 Some(path.to_owned()),
2860 source,
2861 ));
2862 }
2863 }
2864 fs::set_permissions(path, fs::Permissions::from_mode(0o700)).map_err(|source| {
2865 ThumbnailError::io(
2866 "set thumbnail cache directory permissions",
2867 Some(path.to_owned()),
2868 source,
2869 )
2870 })?;
2871 Ok(())
2872 }
2873 Err(source) => Err(ThumbnailError::io(
2874 "inspect thumbnail cache directory",
2875 Some(path.to_owned()),
2876 source,
2877 )),
2878 }
2879}