diff --git a/.github/workflows/update-version.yaml b/.github/workflows/update-version.yaml index 0a33c19..5ee4b0b 100644 --- a/.github/workflows/update-version.yaml +++ b/.github/workflows/update-version.yaml @@ -22,7 +22,9 @@ jobs: run: | sed "s/^version = \".*\"/version = \"${{ steps.vars.outputs.tag }}\"/" Cargo.toml > a ; mv a Cargo.toml sed "s/\$VERSION/${{ steps.vars.outputs.tag }}/g" templates/README.md > a ; mv a README.md - sed "s/ARG VERSION=.*/ARG VERSION=${{ steps.vars.outputs.tag }}/g" Dockerfile > a ; mv a Dockerfile + for i in $(find dockers -name Dockerfile); do + sed "s/ARG VERSION=.*/ARG VERSION=${{ steps.vars.outputs.tag }}/g" $i > a ; mv a $i + done - name: Initialize Git shell: bash diff --git a/Cargo.toml b/Cargo.toml index 3d47ce6..31e4489 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "totebag" -version = "0.5.0" +version = "0.5.2" description = "A tool for extracting/archiving files and directories in multiple formats." repository = "https://github.com/tamada/totebag" readme = "README.md" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index fa3a101..0000000 --- a/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM alpine:3.16 AS builder - -ARG VERSION=0.5.0 -ARG TARGETPLATFORM -ARG PLATFORM=${TARGETPLATFORM#linux/} - -WORKDIR /home/totebag - -RUN apk add --no-cache curl tar gzip \ - && curl -LO https://github.com/tamada/totebag/releases/download/v${VERSION}/totebag-${VERSION}_linux_${PLATFORM}.tar.gz \ - && tar xvfz totebag-${VERSION}_linux_${PLATFORM}.tar.gz - -FROM alpine:3.16 - -ARG VERSION=0.5.0 - -LABEL org.opencontainers.image.source https://github.com/tamada/totebag - -RUN apk add --no-cache libgcc musl-dev \ - && adduser -D nonroot \ - && mkdir -p /workdir - -COPY --from=builder /home/totebag/totebag-${VERSION}/totebag /opt/totebag/totebag - -WORKDIR /workdir -USER nonroot - -ENTRYPOINT [ "/opt/totebag/totebag" ] - diff --git a/Dockerfile b/Dockerfile new file mode 120000 index 0000000..cf497d7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +dockers/Dockerfile.bullseye \ No newline at end of file diff --git a/README.md b/README.md index 4bf73c8..c62a551 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # totebag -[![Version](https://shields.io/badge/Version-0.5.0-blue)](https://github.com/tamada/totebag/releases/tag/v0.5.0) +[![Version](https://shields.io/badge/Version-0.5.2-blue)](https://github.com/tamada/totebag/releases/tag/v0.5.2) [![MIT License](https://shields.io/badge/License-MIT-blue)](https://github.com/tamada/totebag/blob/main/LICENSE) -[![docker](https://shields.io/badge/Docker-0.5.0-blue?logo=docker)](https://github.com/tamada/totebag/pkgs/container/totebag) +[![docker](https://shields.io/badge/Docker-0.5.2-blue?logo=docker)](https://github.com/tamada/totebag/pkgs/container/totebag) [![build](https://github.com/tamada/totebag/actions/workflows/build.yaml/badge.svg)](https://github.com/tamada/totebag/actions/workflows/build.yaml) [![Rust Report Card](https://rust-reportcard.xuri.me/badge/github.com/tamada/totebag)](https://rust-reportcard.xuri.me/report/github.com/tamada/totebag) @@ -58,7 +58,7 @@ brew install tamada/tap/totebag ## :whale: Docker ```sh -docker run -it --rm -v $PWD:/workdir ghcr.io/tamada/totebag:0.5.0 [OPTIONS] [ARGUMENTS]... +docker run -it --rm -v $PWD:/workdir ghcr.io/tamada/totebag:0.5.2 [OPTIONS] [ARGUMENTS]... ``` - **Working directory**: `/workdir` diff --git a/dockers/Dockerfile.bookworm b/dockers/Dockerfile.bookworm new file mode 100644 index 0000000..b78035d --- /dev/null +++ b/dockers/Dockerfile.bookworm @@ -0,0 +1,27 @@ +FROM rust:1-bookworm AS builder + +ARG VERSION=0.4.6 +ARG TARGETPLATFORM + +WORKDIR /work/totebag + +COPY . . +RUN cargo build --release + +FROM debian:bookworm-slim + +ARG VERSION=0.4.6 + +LABEL org.opencontainers.image.source=https://github.com/tamada/totebag \ + org.opencontainers.image.version=${VERSION} \ + org.opencontainers.image.title=totebag \ + org.opencontainers.image.description="totebag is a simple file transfer tool." + +RUN adduser --disabled-password --disabled-login --home /workdir nonroot \ + && mkdir -p /workdir +COPY --from=builder /work/totebag/target/release/totebag /opt/totebag/totebag + +WORKDIR /workdir +USER nonroot + +ENTRYPOINT [ "/opt/totebag/totebag" ] diff --git a/dockers/Dockerfile.bullseye b/dockers/Dockerfile.bullseye new file mode 100644 index 0000000..8f04c8e --- /dev/null +++ b/dockers/Dockerfile.bullseye @@ -0,0 +1,27 @@ +FROM rust:1-bullseye AS builder + +ARG VERSION=0.4.6 +ARG TARGETPLATFORM + +WORKDIR /work/totebag + +COPY . . +RUN cargo build --release + +FROM debian:bullseye-slim + +ARG VERSION=0.4.6 + +LABEL org.opencontainers.image.source=https://github.com/tamada/totebag \ + org.opencontainers.image.version=${VERSION} \ + org.opencontainers.image.title=totebag \ + org.opencontainers.image.description="totebag is a simple file transfer tool." + +RUN adduser --disabled-password --disabled-login --home /workdir nonroot \ + && mkdir -p /workdir +COPY --from=builder /work/totebag/target/release/totebag /opt/totebag/totebag + +WORKDIR /workdir +USER nonroot + +ENTRYPOINT [ "/opt/totebag/totebag" ] diff --git a/dockers/alpine/Dockerfile b/dockers/alpine/Dockerfile index f98731b..74aafba 100644 --- a/dockers/alpine/Dockerfile +++ b/dockers/alpine/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.16 AS builder -ARG VERSION=0.4.6 +ARG VERSION=0.5.2 ARG TARGETPLATFORM ARG PLATFORM=${TARGETPLATFORM#linux/} @@ -12,7 +12,7 @@ RUN apk add --no-cache curl tar gzip \ FROM alpine:3.16 -ARG VERSION=0.4.6 +ARG VERSION=0.5.2 LABEL org.opencontainers.image.source https://github.com/tamada/totebag diff --git a/dockers/distroless/Dockerfile b/dockers/distroless/Dockerfile deleted file mode 100644 index 114fa64..0000000 --- a/dockers/distroless/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM rust:latest AS builder - -ARG VERSION=0.4.6 -ARG TARGETPLATFORM - -WORKDIR /work/totebag - -COPY . . -RUN cargo build --release - -FROM gcr.io/distroless/cc - -ARG VERSION=0.4.6 - -LABEL org.opencontainers.image.source https://github.com/tamada/totebag - -COPY --from=builder /work/totebag/target/release/totebag /opt/totebag/totebag - -WORKDIR /workdir -USER nonroot - -ENTRYPOINT [ "/opt/totebag/totebag" ] diff --git a/slide.md b/slide.md new file mode 100644 index 0000000..483aebd --- /dev/null +++ b/slide.md @@ -0,0 +1,128 @@ +--- +title: "totebag -- A tool for archiving files and directories and extracting several archive formats." +author: "Haruaki Tamada" +slide: true +marp: true +theme: default +--- + +# totebag + +## A tool for extracting/archiving files and directories in several formats + +![](./docs/assets/logo.jpeg) + +Haruaki Tamada (@tamada) +https://github.com/tamada/totebag + +--- + +# Why totebag? + +- 世の中には多くの圧縮,解凍ツールがある. + - しかし,それらを扱うツールの利用方法は統一されていない. +- 各ツールの使い方をいちいち調べるのが面倒だ! +- 一つのツールで,複数の圧縮形式を扱おう! + - 最近の言語は,ライブラリが充実しているのでできるはず! + +--- + +# 作ってみた. + +- **ツール名**  totebag +- **言語**    Rust +- **ロゴ作成**  Microsoft image creator(AI) +- **タグライン** + - A tool for archiving files and directories and extracting several archive formats + +--- + +# サポートしている圧縮形式 + +- Lha/Lzh(解凍のみ) +- Rar(解凍のみ) +- 7Zip +- Tar(tar, tar+gz, tar+xz, tar+bz2) +- Zip(zip, jar, war, ear) + +--- + +# Usage + +```sh +A tool for extracting/archiving files and directories in multiple formats. + +Usage: totebag [OPTIONS] [ARGUMENTS]... + +Arguments: + [ARGUMENTS]... List of files or directories to be processed. + +Options: + -m, --mode Mode of operation. [default: auto] + [possible values: auto, archive, extract, list] + -o, --output Output file in archive mode, or output directory + in extraction mode + --to-archive-name-dir extract files to DEST/ARCHIVE_NAME directory + (extract mode). + -n, --no-recursive No recursive directory (archive mode). + -v, --verbose Display verbose output. + --overwrite Overwrite existing files. + -h, --help Print help + -V, --version Print version +``` + +--- + +# 処理内容 + +- 解凍 + - コマンドライン引数全てが圧縮ファイルだと解凍モードになる. +- リスト + - `mode` に `list` を指定すると,アーカイブ内のファイル一覧を表示する. +- 圧縮 + - `mode` が `auto` で,コマンドライン引数に圧縮ファイル以外が指定されていると圧縮モードになる. + +--- + +# Rust + +- コンパイルがものすごく面倒. + - コンパイルが通るとメモリ関連の実行時エラーがほぼ起こらない. + - だから良い,という意見もわかるし,慣れてくると楽になる. +- けれど,実装に取り掛かるのにものすごく気合がいる言語だなぁ. + +--- + +# Cargo + +- :+1: 言語標準! +- :+1: パッケージマネージャ機能が付いている. +- :+1: 様々な拡張機能が導入可能. +- :-1: 拡張機能の導入に準備が必要. +- :-1: 外部コマンドを実行できない. +- 短気(プログラマの三大美徳の一つ)が満たされない. + - 新たに導入しようとするときに,準備のコマンドを何度も入力しないといけない. + - 環境によって動作する/しないが異なってくる. + +--- + +# 学んだこと + +- Rust + - ある程度思い通りにプログラムを組めるようになった. + - 2年前に学習したときは,非常に苦労した上に,思い通りにならないことが多かった. +- Marp + - いつまでもスライド作りを PowerPoint に頼るのは嫌. + - テキスト形式でスライドを作りたい. + - 授業資料,論文,プログラムなどほぼ全ての成果物をGitHubで管理しているため. + - レイアウトが思い通りにならない... + - 後から pptx を編集できない... + +--- + +# まとめ + +- 複数の圧縮形式を取り扱うツール totebag を作成した. + - Rust で作成した. + - Rust の学習につながった. +- Marp を使い続けるのは様子見かな??? \ No newline at end of file diff --git a/src/extractor.rs b/src/extractor.rs index caebdb2..de287c2 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -31,7 +31,17 @@ impl ExtractorOpts { /// Returns the base of the destination directory for the archive file. /// The target is the archive file name of source. - pub fn destination(&self, target: &PathBuf) -> PathBuf { + pub fn destination(&self, target: &PathBuf) -> Result { + let dest = self.destination_file(target); + println!("destination: {:?}", dest); + if dest.exists() && !self.overwrite { + Err(ToteError::FileExists(dest.clone())) + } else { + Ok(dest) + } + } + + fn destination_file(&self, target: &PathBuf) -> PathBuf { if self.use_archive_name_dir { let file_name = target.file_name().unwrap().to_str().unwrap(); let ext = target.extension().unwrap().to_str().unwrap(); @@ -102,7 +112,10 @@ mod tests { v: create_verboser(false), }; let target = PathBuf::from("/tmp/archive.zip"); - assert_eq!(opts1.destination(&target), PathBuf::from("./archive")); + + if let Ok(t) = opts1.destination(&target) { + assert_eq!(t, PathBuf::from("./archive")); + } let opts2 = ExtractorOpts { dest: PathBuf::from("."), @@ -111,7 +124,9 @@ mod tests { v: create_verboser(false), }; let target = PathBuf::from("/tmp/archive.zip"); - assert_eq!(opts2.destination(&target), PathBuf::from(".")); + if let Ok(t) = opts2.destination(&target) { + assert_eq!(t, PathBuf::from(".")); + } } #[test] diff --git a/src/extractor/lha.rs b/src/extractor/lha.rs index b92393a..2bff33e 100644 --- a/src/extractor/lha.rs +++ b/src/extractor/lha.rs @@ -40,7 +40,7 @@ impl Extractor for LhaExtractor { loop { let header = reader.header(); let name = header.parse_pathname(); - let dest = opts.destination(&archive_file).join(&name); + let dest = opts.destination(&archive_file)?.join(&name); if reader.is_decoder_supported() { opts.v.verbose(format!( "extracting {} ({} bytes)", diff --git a/src/extractor/rar.rs b/src/extractor/rar.rs index 1ce19da..4919978 100644 --- a/src/extractor/rar.rs +++ b/src/extractor/rar.rs @@ -24,7 +24,7 @@ impl Extractor for RarExtractor { let mut file = archive.open_for_processing().unwrap(); while let Some(header) = file.read_header().unwrap() { let name = header.entry().filename.to_str().unwrap(); - let dest = opts.destination(&archive_file).join(PathBuf::from(name)); + let dest = opts.destination(&archive_file)?.join(PathBuf::from(name)); file = if header.entry().is_file() { opts.v.verbose(format!("extracting {} ({} bytes)", name, header.entry().unpacked_size)); create_dir_all(dest.parent().unwrap()).unwrap(); diff --git a/src/extractor/sevenz.rs b/src/extractor/sevenz.rs index 30f566d..e983d68 100644 --- a/src/extractor/sevenz.rs +++ b/src/extractor/sevenz.rs @@ -1,7 +1,8 @@ use std::fs::File; +use std::io::Read; use std::path::PathBuf; -use sevenz_rust::{Archive, BlockDecoder, Password}; +use sevenz_rust::{Archive, BlockDecoder, Password, SevenZArchiveEntry}; use crate::extractor::Extractor; use crate::format::Format; @@ -51,11 +52,11 @@ fn extract(mut file: &File, path: PathBuf, opts: &ExtractorOpts) -> Result<()> { Err(e) => return Err(ToteError::Fatal(Box::new(e))), }; let folder_count = archive.folders.len(); + let mut errs = Vec::::new(); for findex in 0..folder_count { let folder_decoder = BlockDecoder::new(findex, &archive, password.as_slice(), &mut file); if let Err(e) = folder_decoder.for_each_entries(&mut |entry, reader| { - let dest = opts.destination(&path).join(entry.name.clone()); - sevenz_rust::default_entry_extract_fn(entry, reader, &dest) + decode_entry(entry, reader, &mut errs, opts.destination(&path)) }) { return Err(ToteError::Fatal(Box::new(e))) } @@ -63,6 +64,18 @@ fn extract(mut file: &File, path: PathBuf, opts: &ExtractorOpts) -> Result<()> { Ok(()) } +fn decode_entry(entry: &SevenZArchiveEntry, reader: &mut dyn Read, errs: &mut Vec, r: Result) -> std::result::Result { + let dest = match r { + Ok(d) => d, + Err(e) => { + errs.push(e); + return Ok(false); + } + }; + let dest = dest.join(entry.name.clone()); + sevenz_rust::default_entry_extract_fn(entry, reader, &dest) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/extractor/tar.rs b/src/extractor/tar.rs index 0edaec4..f6ae617 100644 --- a/src/extractor/tar.rs +++ b/src/extractor/tar.rs @@ -134,7 +134,7 @@ fn extract_tar( opts.v .verbose(format!("extracting {:?} ({} bytes)", path, size)); - let dest = opts.destination(&original).join(path); + let dest = opts.destination(&original)?.join(path); if entry.header().entry_type().is_file() { create_dir_all(dest.parent().unwrap()).unwrap(); entry.unpack(dest).unwrap(); diff --git a/src/extractor/zip.rs b/src/extractor/zip.rs index 73cc624..ea73fb0 100644 --- a/src/extractor/zip.rs +++ b/src/extractor/zip.rs @@ -26,7 +26,7 @@ impl Extractor for ZipExtractor { fn perform(&self, archive_file: PathBuf, opts: &ExtractorOpts) -> Result<()> { let zip_file = File::open(&archive_file).unwrap(); let mut zip = zip::ZipArchive::new(zip_file).unwrap(); - let dest_base = opts.destination(&archive_file); + let dest_base = opts.destination(&archive_file)?; for i in 0..zip.len() { let mut file = zip.by_index(i).unwrap(); if file.is_file() {