Skip to content

Commit 2ed47d8

Browse files
committed
Implemented zip/tar/tar.zstd compression
1 parent 199a920 commit 2ed47d8

File tree

13 files changed

+140
-56
lines changed

13 files changed

+140
-56
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/tmc-langs-node/jest/tmc.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ test("compresses project", async () => {
138138

139139
const dir = await mockExercise();
140140
expect(fs.existsSync([dir, "output.zip"].join("/"))).toBeFalsy();
141-
tmc.compressProject(dir, [dir, "output.zip"].join("/"), "zip");
141+
tmc.compressProject(dir, [dir, "output.zip"].join("/"), "zip", false);
142142
expect(fs.existsSync([dir, "output.zip"].join("/"))).toBeTruthy();
143143
});
144144

bindings/tmc-langs-node/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,12 @@ fn compress_project(mut cx: FunctionContext) -> JsResult<JsValue> {
100100
cx,
101101
exercise_path: PathBuf,
102102
output_path: PathBuf,
103-
compression: Compression
103+
compression: Compression,
104+
naive: bool
104105
);
105106
lock!(cx, exercise_path);
106107

107-
let res = tmc_langs::compress_project_to(&exercise_path, &output_path, compression);
108+
let res = tmc_langs::compress_project_to(&exercise_path, &output_path, compression, naive);
108109
convert_res(&mut cx, res)
109110
}
110111

bindings/tmc-langs-node/ts/functions.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function checkstyle(
1414
locale: string
1515
): types.StyleValidationResult | null;
1616
export function clean(exercisePath: string): void;
17-
export function compressProject(exercisePath: string, outputPath: string, compression: Compression): void;
17+
export function compressProject(exercisePath: string, outputPath: string, compression: Compression, naive: boolean): void;
1818
export function extractProject(archivePath: string, outputPath: string): void;
1919
export function fastAvailablePoints(exercisePath: string): Array<string>;
2020
export function findExercises(exercisePath: string): Array<string>;

bindings/tmc-langs-node/ts/tmc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export class Tmc {
3838
return tmc.clean(exercisePath);
3939
}
4040

41-
compressProject(exercisePath: string, outputPath: string, compression: Compression): void {
42-
return tmc.compressProject(exercisePath, outputPath, compression);
41+
compressProject(exercisePath: string, outputPath: string, compression: Compression, naive: boolean): void {
42+
return tmc.compressProject(exercisePath, outputPath, compression, naive);
4343
}
4444

4545
extractProject(archivePath: string, outputPath: string): void {

tmc-client/src/tmc_client.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::{
1919
time::Duration,
2020
u32,
2121
};
22-
use tmc_langs_plugins::Language;
22+
use tmc_langs_plugins::{Compression, Language};
2323
use tmc_langs_util::progress_reporter;
2424
#[cfg(feature = "ts")]
2525
use ts_rs::TS;
@@ -265,7 +265,7 @@ impl TmcClient {
265265

266266
// compress
267267
start_stage(2, "Compressing paste submission...", None);
268-
let compressed = tmc_langs_plugins::compress_project_to_zip(submission_path)?;
268+
let compressed = tmc_langs_plugins::compress_project(submission_path, Compression::Zip)?;
269269
progress_stage(
270270
"Compressed paste submission. Posting paste submission...",
271271
None,
@@ -333,7 +333,7 @@ impl TmcClient {
333333
self.require_authentication()?;
334334

335335
start_stage(2, "Compressing submission...", None);
336-
let compressed = tmc_langs_plugins::compress_project_to_zip(submission_path)?;
336+
let compressed = tmc_langs_plugins::compress_project(submission_path, Compression::Zip)?;
337337
progress_stage("Compressed submission. Posting submission...", None);
338338

339339
let result = api_v8::core::submit_exercise(
@@ -556,7 +556,7 @@ impl TmcClient {
556556
) -> Result<NewSubmission, ClientError> {
557557
self.require_authentication()?;
558558

559-
let compressed = tmc_langs_plugins::compress_project_to_zip(submission_path)?;
559+
let compressed = tmc_langs_plugins::compress_project(submission_path, Compression::Zip)?;
560560
let review = if let Some(message) = message_for_reviewer {
561561
ReviewData::WithMessage(message)
562562
} else {

tmc-langs-cli/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ fn run_app(matches: Opt) -> Result<()> {
221221
naive,
222222
} => {
223223
file_util::lock!(exercise_path);
224-
tmc_langs::compress_project_to(&exercise_path, &output_path, compression)?;
224+
tmc_langs::compress_project_to(&exercise_path, &output_path, compression, naive)?;
225225
OutputKind::finished(format!(
226226
"compressed project from {} to {}",
227227
exercise_path.display(),

tmc-langs-framework/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ pub enum TmcError {
1616
ZipName(String),
1717
#[error("Failed to read tar archive")]
1818
TarRead(#[source] std::io::Error),
19+
#[error("Failed to write tar archive")]
20+
TarWrite(#[source] std::io::Error),
1921
#[error("Failed to read zstd archive")]
2022
ZstdRead(#[source] std::io::Error),
23+
#[error("Failed to write zstd archive")]
24+
ZstdWrite(#[source] std::io::Error),
2125

2226
#[error("Failed to read line")]
2327
ReadLine(#[source] std::io::Error),

tmc-langs-plugins/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ tmc-langs-r = { path = "../plugins/r" }
1919

2020
impl-enum = "0.2.0"
2121
log = "0.4.14"
22+
tar = "0.4.38"
2223
thiserror = "1.0.30"
2324
walkdir = "2.3.2"
2425
zip = "0.6.2"
26+
zstd = "0.10.0" # zip 0.6 is still on 0.10
2527

2628
[dev-dependencies]
2729
simple_logger = "2.1.0"

tmc-langs-plugins/src/archive.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use std::{
2+
io::{self, Cursor, Seek, Write},
3+
path::Path,
4+
};
5+
use tar::Builder;
6+
use tmc_langs_framework::{Compression, TmcError};
7+
use tmc_langs_util::file_util;
8+
use zip::{write::FileOptions, ZipWriter};
9+
use zstd::Encoder;
10+
11+
pub enum ArchiveBuilder<W: Write + Seek> {
12+
Tar(Builder<W>),
13+
TarZstd(W, Builder<Cursor<Vec<u8>>>),
14+
Zip(ZipWriter<W>),
15+
}
16+
17+
impl<W: Write + Seek> ArchiveBuilder<W> {
18+
pub fn new(writer: W, compression: Compression) -> Self {
19+
match compression {
20+
Compression::Tar => Self::Tar(Builder::new(writer)),
21+
Compression::TarZstd => Self::TarZstd(writer, Builder::new(Cursor::new(vec![]))),
22+
Compression::Zip => Self::Zip(ZipWriter::new(writer)),
23+
}
24+
}
25+
26+
pub fn add_directory(&mut self, path: &str) -> Result<(), TmcError> {
27+
log::trace!("adding directory {}", path);
28+
match self {
29+
Self::Tar(builder) => {
30+
builder
31+
.append_dir_all(path, path)
32+
.map_err(TmcError::TarWrite)?;
33+
}
34+
Self::TarZstd(_, builder) => {
35+
builder
36+
.append_dir_all(path, path)
37+
.map_err(TmcError::TarWrite)?;
38+
}
39+
Self::Zip(builder) => {
40+
builder.add_directory(path, FileOptions::default().unix_permissions(0o755))?
41+
}
42+
}
43+
Ok(())
44+
}
45+
46+
pub fn add_file(&mut self, source: &Path, target: &str) -> Result<(), TmcError> {
47+
log::trace!("writing file {} as {}", source.display(), target);
48+
match self {
49+
Self::Tar(builder) => builder
50+
.append_path_with_name(source, target)
51+
.map_err(TmcError::TarWrite)?,
52+
Self::TarZstd(_, builder) => builder
53+
.append_path_with_name(source, target)
54+
.map_err(TmcError::TarWrite)?,
55+
Self::Zip(builder) => {
56+
let bytes = file_util::read_file(source)?;
57+
builder.start_file(target, FileOptions::default().unix_permissions(0o755))?;
58+
builder
59+
.write_all(&bytes)
60+
.map_err(|e| TmcError::ZipWrite(source.into(), e))?;
61+
}
62+
}
63+
Ok(())
64+
}
65+
66+
pub fn finish(self) -> Result<W, TmcError> {
67+
let res = match self {
68+
Self::Tar(builder) => builder.into_inner().map_err(TmcError::TarWrite)?,
69+
Self::TarZstd(writer, builder) => {
70+
let mut tar_data = builder.into_inner().map_err(TmcError::TarWrite)?;
71+
let mut encoder = Encoder::new(writer, 0).map_err(TmcError::ZstdWrite)?;
72+
io::copy(&mut tar_data, &mut encoder).map_err(TmcError::ZstdWrite)?;
73+
encoder.finish().map_err(TmcError::ZstdWrite)?
74+
}
75+
Self::Zip(mut builder) => builder.finish()?,
76+
};
77+
Ok(res)
78+
}
79+
}

0 commit comments

Comments
 (0)