libmedusa_zip/
destination.rs

1/*
2 * Description: ???
3 *
4 * Copyright (C) 2023 Danny McClanahan <dmcC2@hypnicjerk.ai>
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0 (see LICENSE).
8 */
9
10//! ???
11
12use displaydoc::Display;
13use parking_lot::Mutex;
14use thiserror::Error;
15use tokio::{
16  fs,
17  io::{self, AsyncSeekExt},
18  task,
19};
20use zip::{result::ZipError, ZipWriter};
21
22use std::{ops::DerefMut, path::Path, sync::Arc};
23
24#[derive(Debug, Display, Error)]
25pub enum DestinationError {
26  /// i/o error accessing destination file: {0}
27  Io(#[from] io::Error),
28  /// error setting up zip format in destination file: {0}
29  Zip(#[from] ZipError),
30  /// error joining zip setup task: {0}
31  Join(#[from] task::JoinError),
32}
33
34#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
35pub enum DestinationBehavior {
36  /// Create the file if new, or truncate it if it exists.
37  #[default]
38  AlwaysTruncate,
39  /// Initialize an existing zip file.
40  AppendOrFail,
41  /// Append if the file already exists, otherwise create it.
42  OptimisticallyAppend,
43  /// Open the file in append mode, but don't try to read any zip info from it.
44  ///
45  /// This is useful for creating e.g. PEX files or other self-executing zips
46  /// with a shebang line.
47  AppendToNonZip,
48}
49
50impl DestinationBehavior {
51  pub async fn initialize(self, path: &Path) -> Result<ZipWriter<std::fs::File>, DestinationError> {
52    let (file, with_append) = match self {
53      Self::AlwaysTruncate => {
54        let f = fs::OpenOptions::new()
55          .write(true)
56          .create(true)
57          .truncate(true)
58          .open(path)
59          .await?;
60        (f, false)
61      },
62      Self::AppendOrFail => {
63        let f = fs::OpenOptions::new()
64          .write(true)
65          .read(true)
66          .open(path)
67          .await?;
68        (f, true)
69      },
70      Self::OptimisticallyAppend => {
71        match fs::OpenOptions::new()
72          .write(true)
73          .create_new(true)
74          .open(path)
75          .await
76        {
77          Ok(f) => (f, false),
78          Err(e) => match e.kind() {
79            io::ErrorKind::AlreadyExists => {
80              let f = fs::OpenOptions::new()
81                .write(true)
82                .read(true)
83                .open(path)
84                .await?;
85              (f, true)
86            },
87            _ => {
88              return Err(e.into());
89            },
90          },
91        }
92      },
93      Self::AppendToNonZip => {
94        let mut f = fs::OpenOptions::new()
95          .write(true)
96          .read(true)
97          .open(path)
98          .await?;
99        /* NB: do NOT!!! open the file for append!!! It will only BREAK EVERYTHING IN
100         * MYSTERIOUS WAYS by constantly moving the seek cursor! Opening with
101         * ::new_append() will seek to the end for us, but in this case we
102         * want to write to a file that *doesn't* already have zip
103         * data, so we need to tell the file handle to go to the end before giving it
104         * to the zip library. */
105        f.seek(io::SeekFrom::End(0)).await?;
106        (f, false)
107      },
108    };
109    let file = file.into_std().await;
110
111    let writer = task::spawn_blocking(move || {
112      if with_append {
113        Ok::<_, DestinationError>(ZipWriter::new_append(file)?)
114      } else {
115        Ok(ZipWriter::new(file))
116      }
117    })
118    .await??;
119
120    Ok(writer)
121  }
122}
123
124pub struct OutputWrapper<O> {
125  handle: Arc<Mutex<O>>,
126}
127
128impl<O> Clone for OutputWrapper<O> {
129  fn clone(&self) -> Self {
130    Self {
131      handle: Arc::clone(&self.handle),
132    }
133  }
134}
135
136impl<O> OutputWrapper<O> {
137  pub fn wrap(writer: O) -> Self {
138    Self {
139      handle: Arc::new(Mutex::new(writer)),
140    }
141  }
142
143  pub fn reclaim(self) -> O {
144    Arc::into_inner(self.handle)
145      .expect("expected this to be the last strong ref")
146      .into_inner()
147  }
148
149  pub fn lease(&self) -> impl DerefMut<Target=O>+'_ { self.handle.lock() }
150}