Skip to content

Commit ebb028a

Browse files
committed
Fully working (locally)
1 parent 5a1fee9 commit ebb028a

5 files changed

Lines changed: 128 additions & 21 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/target
2-
example_repo
2+
example_*

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ readme = "README.md"
1111
edition = "2024"
1212

1313
[dependencies]
14+
rand = "0.9.1"
1415
serde = { version = "1.0.219", features = ["derive"] }
1516
serde_json = "1.0.140"
1617
walkdir = { version = "2.5.0", optional = true }
@@ -20,4 +21,4 @@ zstd = "0.13.3"
2021
[features]
2122
encoding = ["dep:walkdir"]
2223
decoding = []
23-
https = []
24+
https = []

src/lib.rs

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use std::{
2-
fs::{self, File},
3-
io::Read,
4-
os::unix::fs::PermissionsExt,
2+
fs::{self, create_dir_all, rename},
3+
os::unix::fs::{PermissionsExt, symlink},
54
path::Path,
65
};
76

7+
use rand::RngCore;
8+
89
use crate::{artifacts::get_artifact, compression::decompress_file};
910

1011
mod artifacts;
@@ -64,6 +65,7 @@ pub fn build(input_dir: &Path, repo_dir: &Path, artifact_name: &String) -> Resul
6465
{
6566
use std::os::unix::fs::PermissionsExt;
6667

68+
let root_path = input_dir.to_string_lossy().to_string();
6769
let path = entry.path().to_string_lossy().to_string();
6870
let raw = fs::read(&path).expect("Couldn't read file for chunking!");
6971
let compressed = compression::compress_file(&raw, 3);
@@ -75,7 +77,7 @@ pub fn build(input_dir: &Path, repo_dir: &Path, artifact_name: &String) -> Resul
7577
// Save the chunk
7678
fs::write(chunk_dir.join(&hash), compressed).expect("Couldn't write chunk file!");
7779

78-
files.push((path, hash, is_executable));
80+
files.push((path.replacen(&root_path, "", 1), hash, is_executable));
7981
}
8082

8183
let manifest = Manifest {
@@ -102,28 +104,64 @@ pub fn build(input_dir: &Path, repo_dir: &Path, artifact_name: &String) -> Resul
102104
}
103105

104106
pub fn install_artifact(artifact_name: &String, store_path: &Path, repo_cache_path: &Path) {
105-
let chunk_dir = repo_cache_path.join("chunks");
106-
let manifest_dir = repo_cache_path.join("manifests");
107-
let artifacts_path = repo_cache_path.join("artifacts");
107+
assert!(store_path.is_absolute(), "Store path must be absolute!");
108+
assert!(
109+
repo_cache_path.is_absolute(),
110+
"Repo cache path must be absolute!"
111+
);
112+
113+
let repo_manifest_dir = repo_cache_path.join("manifests");
114+
let repo_artifacts_path = repo_cache_path.join("artifacts");
115+
116+
let store_chunk_dir = store_path.join("chunks");
117+
let store_manifest_dir = store_path.join("manifests");
118+
let store_artifacts_path = store_path.join("artifacts");
108119

109-
let manifest_hash = get_artifact(artifact_name, &artifacts_path).unwrap();
120+
let manifest_hash =
121+
get_artifact(artifact_name, &repo_artifacts_path).expect("Artifact not found");
110122

111123
let manifest: Manifest = serde_json::from_str(
112-
fs::read_to_string(manifest_dir.join(manifest_hash))
124+
fs::read_to_string(repo_manifest_dir.join(&manifest_hash))
113125
.unwrap()
114126
.as_str(),
115127
)
116128
.unwrap();
117129

118-
for (path, hash, executable) in manifest.files {
130+
for (_path, hash, executable) in &manifest.files {
119131
// Install chunks
120132
install_chunk(&hash, store_path, repo_cache_path).unwrap();
121133

122134
// Make sure it's executable if it needs to be
123-
if executable {
135+
if *executable {
124136
make_chunk_executable(&hash, store_path);
125137
}
126138
}
139+
140+
// Seperate to ensure chunks have been installed prior to linked
141+
for (manifest_defined_path, hash, _executable) in &manifest.files {
142+
let manifest_defined_path = manifest_defined_path.trim_start_matches('/').to_string();
143+
144+
let path = store_manifest_dir
145+
.join(&manifest_hash)
146+
.join(manifest_defined_path);
147+
148+
if !path.parent().unwrap().exists() {
149+
create_dir_all(&path.parent().unwrap()).unwrap();
150+
}
151+
152+
if !fs::exists(&path).unwrap() {
153+
symlink(store_chunk_dir.as_path().join(hash), path).unwrap();
154+
}
155+
}
156+
157+
// Create a temporary symlink for atomic update
158+
let tmp_file_name = format!(".tmp_{}", rand::rng().next_u64());
159+
160+
let tmp_symlink = store_artifacts_path.join(&tmp_file_name);
161+
let final_symlink = store_artifacts_path.join(artifact_name);
162+
163+
symlink(store_manifest_dir.join(&manifest_hash), &tmp_symlink).unwrap();
164+
rename(&tmp_symlink, &final_symlink).unwrap();
127165
}
128166

129167
fn make_chunk_executable(chunk_hash: &String, store_path: &Path) {
@@ -160,7 +198,9 @@ fn install_chunk(
160198
}
161199

162200
fs::write(store_chunk_path, decompressed_chunk).unwrap();
163-
}
164201

165-
Err("Couldn't find chunk".to_string())
202+
Ok(())
203+
} else {
204+
Err("Couldn't find chunk".to_string())
205+
}
166206
}

tests/basic.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ use std::{fs, path::Path};
33
#[cfg(feature = "encoding")]
44
#[test]
55
fn main() {
6+
use std::path::absolute;
7+
68
use lcas::{build, create_repo, create_store, install_artifact};
79

8-
let input_dir = Path::new("./example_dir");
9-
let repo_dir = Path::new("./example_repo");
10-
let store_dir = Path::new("./example_store");
10+
let input_dir = absolute(Path::new("./example_dir")).unwrap();
11+
let repo_dir = absolute(Path::new("./example_repo")).unwrap();
12+
let store_dir = absolute(Path::new("./example_store")).unwrap();
1113

1214
if !repo_dir.exists() {
13-
create_repo(repo_dir).unwrap();
15+
create_repo(repo_dir.as_path()).unwrap();
1416
}
1517
if !store_dir.exists() {
1618
create_store(&store_dir).unwrap();
@@ -24,7 +26,12 @@ fn main() {
2426
)
2527
.unwrap();
2628

27-
build(input_dir, repo_dir, &"generic".to_string()).expect("Build Failure");
29+
build(
30+
input_dir.as_path(),
31+
repo_dir.as_path(),
32+
&"generic".to_string(),
33+
)
34+
.expect("Build Failure");
2835

29-
install_artifact(&"generic".to_string(), store_dir, &repo_dir);
36+
install_artifact(&"generic".to_string(), store_dir.as_path(), &repo_dir);
3037
}

0 commit comments

Comments
 (0)