Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New rnote file format, better compression and serialization, atomic file saving #1177

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
689aaa0
zstd comp + decomp functions
anesthetice Aug 7, 2024
1b64bfb
deps that I forgot to commit
anesthetice Aug 7, 2024
11fb351
simpler than I thought + backwards compatible
anesthetice Aug 8, 2024
a5ae6b9
removed tracing code and compress_to_gzip
anesthetice Aug 8, 2024
11da431
starting to take shape
anesthetice Aug 14, 2024
eab08bb
far from over, but it works at least
anesthetice Aug 14, 2024
b1195cb
lots of clean up, set max header size to u32::MAX instead of u16::MAX…
anesthetice Aug 16, 2024
89704a0
meson.build fix, forward-comp header
anesthetice Aug 16, 2024
a5d63d8
testing code, will be removed later, bitcode seems really good
anesthetice Aug 16, 2024
bdfc910
kept postcard, json, bitcode, bincode
anesthetice Aug 16, 2024
a01c7f1
customizable compression levels
anesthetice Aug 18, 2024
07cad72
[in progress] CompressionLevel, SavePrefs
anesthetice Aug 19, 2024
e7a7f1a
made EngineConfig not sync across tabs (was very unintuitive)
anesthetice Aug 19, 2024
887bd46
mutate command for rnote-cli, removed option wrapper in EngineSnapsho…
anesthetice Aug 20, 2024
8ae90af
conformed_saveprefs
anesthetice Aug 20, 2024
b9437f5
comments, rnote doesn't crash if None is selected as a CompressionLev…
anesthetice Aug 20, 2024
9b72185
removed tracing code, made Compression Level invisible if set to None
anesthetice Aug 20, 2024
531db89
minor changes
anesthetice Aug 20, 2024
b13c27e
serde renames for consistency
anesthetice Aug 20, 2024
0b4035a
Ijson, + big issue fix on mutate
anesthetice Aug 21, 2024
e83d177
(improved) two step file save process, lazy evaluation (in-progress)
anesthetice Aug 25, 2024
df3459b
formatting, lazy evals
anesthetice Aug 25, 2024
e004f8b
comments, prettier code, faster legacy loading time
anesthetice Aug 25, 2024
48ed535
re-added the slower less efficient conversion for rnote-cli mutate
anesthetice Aug 25, 2024
607ff75
removed bincode + postcard
anesthetice Aug 25, 2024
40131ed
renamed CompM to CompressionMethod and SerM to SerializationMethod
anesthetice Sep 2, 2024
b8062ec
one-to-one repr of semver::Version in PRELUE
anesthetice Sep 2, 2024
4d2d1ac
more readable code and errors, concat, tested an owned from_bytes and…
anesthetice Sep 6, 2024
4bca7e6
wasn't a need to skip serializing the SavePrefs when ser. the engine …
anesthetice Sep 8, 2024
fcb116f
misc, minor changes
anesthetice Sep 9, 2024
4026a73
Merge branch 'main' into file
anesthetice Sep 17, 2024
f3b5c93
missed closing delim
anesthetice Sep 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ anyhow = "1.0"
approx = "0.5.1"
async-fs = "2.1"
base64 = "0.22.1"
bitcode = { version = "0.6", features = ["serde"] }
cairo-rs = { version = "0.20.1", features = ["v1_18", "png", "svg", "pdf"] }
chrono = "0.4.38"
clap = { version = "4.5", features = ["derive"] }
crc32fast = { version = "1.4" }
dialoguer = "0.11.0"
flate2 = "1.0"
fs_extra = "1.3"
Expand Down Expand Up @@ -85,6 +87,7 @@ winresource = "0.1.17"
xmlwriter = "0.1.0"
# Enabling feature > v20_9 causes linker errors on mingw
poppler-rs = { version = "0.24.1", features = ["v20_9"] }
zstd = { version = "0.13", features = ["zstdmt"] }

[patch.crates-io]
# once a new piet (current v0.6.2) is released with updated cairo and kurbo deps, this can be removed.
Expand Down
47 changes: 46 additions & 1 deletion crates/rnote-cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Imports
use crate::{export, import, test};
use crate::{export, import, mutate, test};
use anyhow::Context;
use clap::builder::PossibleValuesParser;
use clap::Parser;
use rnote_compose::SplitOrder;
use rnote_engine::engine::export::{
DocExportFormat, DocPagesExportFormat, DocPagesExportPrefs, SelectionExportFormat,
SelectionExportPrefs,
};
use rnote_engine::engine::import::XoppImportPrefs;
use rnote_engine::fileformats::rnoteformat;
use rnote_engine::SelectionCollision;
use smol::fs::File;
use smol::io::{AsyncReadExt, AsyncWriteExt};
Expand Down Expand Up @@ -69,6 +71,27 @@ pub(crate) enum Command {
#[arg(long, action = clap::ArgAction::SetTrue, global = true)]
open: bool,
},
/// Mutates one or more of the following for the specified Rnote file(s):{n}
/// compression method, compression level, serialization method, method lock
Mutate {
/// The rnote save file(s) to mutate
rnote_files: Vec<PathBuf>,
/// Keep the original rnote save file(s)
#[arg(long = "not-in-place", alias = "nip", action = clap::ArgAction::SetTrue)]
not_in_place: bool,
/// Sets method_lock to true, allowing a rnote save file to keep using non-default methods to serialize and compress itself
#[arg(short = 'l', long, action = clap::ArgAction::SetTrue, conflicts_with = "unlock")]
lock: bool,
/// Sets method_lock to false, coercing the file to use default methods on the next save
#[arg(short = 'u', long, action = clap::ArgAction::SetTrue, conflicts_with = "lock")]
unlock: bool,
#[arg(short = 's', long, action = clap::ArgAction::Set, value_parser = PossibleValuesParser::new(rnoteformat::SerializationMethod::VALID_STR_ARRAY))]
serialization_method: Option<String>,
#[arg(short = 'c', long, action = clap::ArgAction::Set, value_parser = PossibleValuesParser::new(rnoteformat::CompressionMethod::VALID_STR_ARRAY))]
compression_method: Option<String>,
#[arg(short = 'v', long, action = clap::ArgAction::Set)]
compression_level: Option<u8>,
},
}

#[derive(clap::ValueEnum, Debug, Clone, Copy, Default)]
Expand Down Expand Up @@ -242,6 +265,28 @@ pub(crate) async fn run() -> anyhow::Result<()> {
.await?;
println!("Export finished!");
}
Command::Mutate {
rnote_files,
not_in_place,
lock,
unlock,
serialization_method,
compression_method,
compression_level,
} => {
println!("Mutating..\n");
mutate::run_mutate(
rnote_files,
not_in_place,
lock,
unlock,
serialization_method,
compression_method,
compression_level,
)
.await?;
println!("Mutate finished!");
}
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions crates/rnote-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
pub(crate) mod cli;
pub(crate) mod export;
pub(crate) mod import;
pub(crate) mod mutate;
pub(crate) mod test;
pub(crate) mod validators;

Expand Down
1 change: 1 addition & 0 deletions crates/rnote-cli/src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ rnote_cli_sources = files(
'export.rs',
'import.rs',
'main.rs',
'mutate.rs',
'test.rs',
'validators.rs',
)
94 changes: 94 additions & 0 deletions crates/rnote-cli/src/mutate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use rnote_engine::engine::EngineSnapshot;
use rnote_engine::fileformats::rnoteformat::RnoteHeader;
use rnote_engine::fileformats::FileFormatSaver;
use rnote_engine::fileformats::{rnoteformat::RnoteFile, FileFormatLoader};
use smol::{fs::OpenOptions, io::AsyncReadExt};
use std::path::PathBuf;
use std::str::FromStr;

pub(crate) async fn run_mutate(
rnote_files: Vec<PathBuf>,
not_in_place: bool,
lock: bool,
unlock: bool,
serialization_method: Option<String>,
compression_method: Option<String>,
compression_level: Option<u8>,
) -> anyhow::Result<()> {
let total_len = rnote_files.len();
let mut total_delta: f64 = 0.0;
for (idx, mut filepath) in rnote_files.into_iter().enumerate() {
println!("Working on file {} out of {}", idx + 1, total_len);

let file_read_operation = async {
let mut read_file = OpenOptions::new().read(true).open(&filepath).await?;

let mut bytes: Vec<u8> = {
match read_file.metadata().await {
Ok(metadata) => {
Vec::with_capacity(usize::try_from(metadata.len()).unwrap_or(usize::MAX))
}
Err(err) => {
eprintln!("Failed to read file metadata, '{err}'");
Vec::new()
}
}
};

read_file.read_to_end(&mut bytes).await?;
Ok::<Vec<u8>, anyhow::Error>(bytes)
};
let bytes = file_read_operation.await?;
let old_size_mb = bytes.len() as f64 / 1e6;
let rnote_file = RnoteFile::load_from_bytes(&bytes)?;

let serialization = if let Some(ref str) = serialization_method {
rnote_engine::fileformats::rnoteformat::SerializationMethod::from_str(str).unwrap()
} else {
rnote_file.header.serialization
};

let mut compression = if let Some(ref str) = compression_method {
rnote_engine::fileformats::rnoteformat::CompressionMethod::from_str(str).unwrap()
} else {
rnote_file.header.compression
};

if let Some(lvl) = compression_level {
compression.update_compression_level(lvl)?;
}

let method_lock = (rnote_file.header.method_lock | lock) && !unlock;
let uc_data = serialization.serialize(&EngineSnapshot::try_from(rnote_file)?)?;
let uc_size = uc_data.len() as u64;
let data = compression.compress(uc_data)?;

let rnote_file = RnoteFile {
header: RnoteHeader {
serialization,
compression,
uc_size,
method_lock,
},
body: data,
};

if not_in_place {
let file_stem = filepath
.file_stem()
.ok_or_else(|| anyhow::anyhow!("File does not contain a valid file stem"))?
.to_str()
.ok_or_else(|| anyhow::anyhow!("File does not contain a valid file stem"))?;
filepath.set_file_name(format!("{}_mut.rnote", file_stem));
}

let data = rnote_file.save_as_bytes("")?;
let new_size_mb = data.len() as f64 / 1e6;
rnote_engine::utils::atomic_save_to_file(&filepath, &data).await?;

println!("{:.2} MB → {:.2} MB", old_size_mb, new_size_mb,);
total_delta += new_size_mb - old_size_mb;
}
println!("\n⇒ ∆ = {:.2} MB", total_delta);
Ok(())
}
4 changes: 4 additions & 0 deletions crates/rnote-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ rnote-compose = { workspace = true }

anyhow = { workspace = true }
approx = { workspace = true }
async-fs = { workspace = true }
base64 = { workspace = true }
bitcode = { workspace = true, features = ["serde"] }
cairo-rs = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, optional = true }
crc32fast = { workspace = true }
flate2 = { workspace = true }
futures = { workspace = true }
geo = { workspace = true }
Expand Down Expand Up @@ -55,6 +58,7 @@ tracing = { workspace = true }
unicode-segmentation = { workspace = true }
usvg = { workspace = true }
xmlwriter = { workspace = true }
zstd = { workspace = true, features = ["zstdmt"] }
# the long-term plan is to remove the gtk4 dependency entirely after switching to another renderer.
gtk4 = { workspace = true, optional = true }

Expand Down
5 changes: 2 additions & 3 deletions crates/rnote-engine/src/engine/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,7 @@ impl Engine {
let engine_snapshot = self.take_snapshot();
rayon::spawn(move || {
let result = || -> anyhow::Result<Vec<u8>> {
let rnote_file = RnoteFile {
engine_snapshot: ijson::to_value(&engine_snapshot)?,
};
let rnote_file = RnoteFile::try_from(&engine_snapshot)?;
rnote_file.save_as_bytes(&file_name)
};
if oneshot_sender.send(result()).is_err() {
Expand All @@ -353,6 +351,7 @@ impl Engine {
penholder: self.penholder.clone_config(),
import_prefs: self.import_prefs.clone_config(),
export_prefs: self.export_prefs.clone_config(),
save_prefs: self.save_prefs.clone_conformed_config(),
pen_sounds: self.pen_sounds(),
optimize_epd: self.optimize_epd(),
}
Expand Down
1 change: 1 addition & 0 deletions crates/rnote-engine/src/engine/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ impl Engine {
self.penholder = engine_config.penholder;
self.import_prefs = engine_config.import_prefs;
self.export_prefs = engine_config.export_prefs;
self.save_prefs = engine_config.save_prefs;

// Set the pen sounds to update the audioplayer
self.set_pen_sounds(engine_config.pen_sounds, data_dir);
Expand Down
Loading