summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs1154
1 files changed, 1154 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..3a8e5f9
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,1154 @@
1use std::{
2 collections::{HashMap, VecDeque},
3 fmt::Display,
4 fs::File,
5 io::{BufWriter, Read as _, Write},
6 os::unix::fs::MetadataExt,
7 path::{Path, PathBuf},
8 str::FromStr,
9 sync::{Arc, Mutex},
10 time::SystemTime,
11};
12
13use clap::{Args, Parser, Subcommand};
14use sha2::Digest;
15use slotmap::SlotMap;
16
17const BLOB_STORE_HIERARCHY_DEPTH: usize = 4;
18
19#[derive(Debug)]
20pub struct InvalidBlobId;
21
22impl Display for InvalidBlobId {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "invalid blob id")
25 }
26}
27
28impl std::error::Error for InvalidBlobId {}
29
30#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
31pub struct BlobId([u8; 32]);
32
33impl BlobId {
34 pub fn hash(&self, data: &[u8]) -> BlobId {
35 let mut hasher = sha2::Sha256::default();
36 hasher.write_all(data).unwrap();
37 Self(hasher.finalize().try_into().unwrap())
38 }
39
40 pub fn as_bytes(&self) -> &[u8; 32] {
41 &self.0
42 }
43}
44
45impl Display for BlobId {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 hex::display(&self.0).fmt(f)
48 }
49}
50
51impl FromStr for BlobId {
52 type Err = InvalidBlobId;
53
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
55 let decoded = hex::decode(s).map_err(|_| InvalidBlobId)?;
56 Ok(Self(decoded.try_into().map_err(|_| InvalidBlobId)?))
57 }
58}
59
60#[derive(Debug, Clone)]
61pub struct BlobStore(PathBuf);
62
63impl BlobStore {
64 pub fn new(path: impl Into<PathBuf>) -> Self {
65 Self(path.into())
66 }
67}
68
69pub fn blob_path(store: &BlobStore, blob_id: &BlobId) -> PathBuf {
70 let encoded = hex::encode(blob_id.as_bytes());
71 let mut path = store.0.clone();
72 for depth in 0..BLOB_STORE_HIERARCHY_DEPTH {
73 let base_idx = depth * 2;
74 path.push(&encoded[base_idx..base_idx + 2]);
75 }
76 path.push(encoded);
77 path
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
81pub enum ImportMode {
82 Move,
83 Copy,
84 HardLink,
85}
86
87pub fn blob_import_file(
88 store: &BlobStore,
89 mode: ImportMode,
90 blob_id: &BlobId,
91 file_path: &Path,
92) -> std::io::Result<()> {
93 let blob_path = blob_path(store, blob_id);
94 if blob_path.exists() {
95 return Ok(());
96 }
97
98 if let Some(parent) = blob_path.parent() {
99 std::fs::create_dir_all(parent)?;
100 }
101 match mode {
102 ImportMode::Move => {
103 std::fs::rename(file_path, blob_path)?;
104 }
105 ImportMode::Copy => {
106 todo!()
107 }
108 ImportMode::HardLink => match std::fs::hard_link(file_path, blob_path) {
109 Ok(()) => {}
110 Err(err) => {
111 if err.kind() != std::io::ErrorKind::AlreadyExists {
112 panic!("{err}");
113 }
114 }
115 },
116 }
117 Ok(())
118}
119
120pub fn blob_hash_file(file_path: &Path) -> std::io::Result<BlobId> {
121 let mut file = std::fs::File::open(file_path)?;
122 let mut buf = vec![0u8; 1 * 1024 * 1024];
123 let mut hasher = sha2::Sha256::default();
124 loop {
125 let n = file.read(&mut buf)?;
126 if n == 0 {
127 break;
128 }
129 hasher.write_all(&buf[..n])?;
130 }
131 Ok(BlobId(hasher.finalize().try_into().unwrap()))
132}
133
134pub fn blob_size(store: &BlobStore, blob_id: &BlobId) -> std::io::Result<u64> {
135 let blob_path = blob_path(store, blob_id);
136 Ok(blob_path.metadata()?.size())
137}
138
139const OPERATION_KIND_CREATE_FILE: &'static str = "create_file";
140const OPERATION_KIND_CREATE_DIR: &'static str = "create_dir";
141const OPERATION_KIND_REMOVE: &'static str = "remove";
142const OPERATION_KIND_RENAME: &'static str = "rename";
143
144#[derive(Debug)]
145pub struct InvalidOperation;
146
147#[derive(Debug, Clone, PartialEq, Eq, Hash)]
148pub struct Operation {
149 pub header: OperationHeader,
150 pub data: OperationData,
151}
152
153impl FromStr for Operation {
154 type Err = InvalidOperation;
155
156 fn from_str(s: &str) -> Result<Self, Self::Err> {
157 let err = || InvalidOperation;
158 let mut iter = s.split('\t');
159
160 let timestamp_str = iter.next().ok_or_else(err)?;
161 let revision_str = iter.next().ok_or_else(err)?;
162 let email_str = iter.next().ok_or_else(err)?;
163 let kind_str = iter.next().ok_or_else(err)?;
164
165 let timestamp = timestamp_str.parse().map_err(|_| err())?;
166 let revision = revision_str.parse().map_err(|_| err())?;
167 let email = email_str.to_string();
168 let data = match kind_str {
169 OPERATION_KIND_CREATE_FILE => {
170 let path_str = iter.next().ok_or_else(err)?;
171 let blob_str = iter.next().ok_or_else(err)?;
172 let size_str = iter.next().ok_or_else(err)?;
173
174 let path = path_str.parse().map_err(|_| err())?;
175 let blob = blob_str.parse().map_err(|_| err())?;
176 let size = size_str.parse().map_err(|_| err())?;
177
178 OperationData::CreateFile(OperationCreateFile { path, blob, size })
179 }
180 OPERATION_KIND_CREATE_DIR => {
181 let path_str = iter.next().ok_or_else(err)?;
182
183 let path = path_str.parse().map_err(|_| err())?;
184
185 OperationData::CreateDir(OperationCreateDir { path })
186 }
187 OPERATION_KIND_RENAME => {
188 let old_str = iter.next().ok_or_else(err)?;
189 let new_str = iter.next().ok_or_else(err)?;
190
191 let old = old_str.parse().map_err(|_| err())?;
192 let new = new_str.parse().map_err(|_| err())?;
193
194 OperationData::Rename(OperationRename { old, new })
195 }
196 OPERATION_KIND_REMOVE => {
197 let path_str = iter.next().ok_or_else(err)?;
198
199 let path = path_str.parse().map_err(|_| err())?;
200
201 OperationData::Remove(OperationRemove { path })
202 }
203 _ => return Err(err()),
204 };
205
206 Ok(Self {
207 header: OperationHeader {
208 timestamp,
209 revision,
210 email,
211 },
212 data,
213 })
214 }
215}
216
217impl Display for Operation {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 write!(
220 f,
221 "{}\t{}\t{}\t",
222 self.header.timestamp, self.header.revision, self.header.email
223 )?;
224 match &self.data {
225 OperationData::CreateFile(data) => write!(
226 f,
227 "{}\t{}\t{}\t{}",
228 OPERATION_KIND_CREATE_FILE, data.path, data.blob, data.size
229 ),
230 OperationData::CreateDir(data) => {
231 write!(f, "{}\t{}", OPERATION_KIND_CREATE_DIR, data.path)
232 }
233 OperationData::Remove(data) => write!(f, "{}\t{}", OPERATION_KIND_REMOVE, data.path),
234 OperationData::Rename(data) => {
235 write!(f, "{}\t{}\t{}", OPERATION_KIND_RENAME, data.old, data.new)
236 }
237 }
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Hash)]
242pub struct OperationHeader {
243 pub timestamp: u64,
244 pub revision: u64,
245 pub email: String,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Hash)]
249pub enum OperationData {
250 CreateFile(OperationCreateFile),
251 CreateDir(OperationCreateDir),
252 Remove(OperationRemove),
253 Rename(OperationRename),
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, Hash)]
257pub struct OperationCreateFile {
258 pub path: DrivePath,
259 pub blob: BlobId,
260 pub size: u64,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Hash)]
264pub struct OperationCreateDir {
265 pub path: DrivePath,
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Hash)]
269pub struct OperationRemove {
270 pub path: DrivePath,
271}
272
273#[derive(Debug, Clone, PartialEq, Eq, Hash)]
274pub struct OperationRename {
275 pub old: DrivePath,
276 pub new: DrivePath,
277}
278
279#[derive(Debug)]
280pub struct InvalidDrivePath(String);
281
282impl std::fmt::Display for InvalidDrivePath {
283 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284 write!(f, "invalid path: {}", self.0)
285 }
286}
287
288impl std::error::Error for InvalidDrivePath {}
289
290#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
291pub struct DrivePath(Vec<DrivePathComponent>);
292
293impl Display for DrivePath {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 if self.is_root() {
296 f.write_str("/")?;
297 } else {
298 for comp in self.components() {
299 f.write_str("/")?;
300 comp.fmt(f)?;
301 }
302 }
303 Ok(())
304 }
305}
306
307impl DrivePath {
308 pub fn is_root(&self) -> bool {
309 self.0.is_empty()
310 }
311
312 pub fn push(&self, component: DrivePathComponent) -> DrivePath {
313 let mut components = self.0.clone();
314 components.push(component);
315 Self(components)
316 }
317
318 pub fn components(&self) -> &[DrivePathComponent] {
319 self.0.as_slice()
320 }
321
322 pub fn parent(&self) -> DrivePath {
323 if self.0.is_empty() {
324 Self(Default::default())
325 } else {
326 let slice = self.0.as_slice();
327 Self(slice[..slice.len() - 1].to_owned())
328 }
329 }
330
331 pub fn last(&self) -> Option<DrivePathComponent> {
332 self.0.last().cloned()
333 }
334
335 pub fn split(&self) -> Option<(DrivePath, DrivePathComponent)> {
336 if self.0.is_empty() {
337 None
338 } else {
339 Some((
340 Self(self.0[..self.0.len() - 1].to_owned()),
341 self.0[self.0.len() - 1].clone(),
342 ))
343 }
344 }
345}
346
347impl FromStr for DrivePath {
348 type Err = InvalidDrivePath;
349
350 fn from_str(s: &str) -> Result<Self, Self::Err> {
351 let mut components = Vec::default();
352 for unchecked_component in s.trim().trim_matches('/').split('/') {
353 match unchecked_component.parse() {
354 Ok(component) => components.push(component),
355 Err(err) => {
356 return Err(InvalidDrivePath(format!(
357 "path contained invalid component '{unchecked_component}': {err}"
358 )));
359 }
360 };
361 }
362 Ok(Self(components))
363 }
364}
365
366#[derive(Debug)]
367pub struct InvalidDrivePathComponent(&'static str);
368
369impl Display for InvalidDrivePathComponent {
370 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371 self.0.fmt(f)
372 }
373}
374
375impl InvalidDrivePathComponent {
376 pub const fn new(msg: &'static str) -> Self {
377 Self(msg)
378 }
379}
380
381#[derive(Debug, Clone, PartialEq, Eq, Hash)]
382pub struct DrivePathComponent(String);
383
384impl Display for DrivePathComponent {
385 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
386 self.0.fmt(f)
387 }
388}
389
390impl DrivePathComponent {
391 pub fn as_str(&self) -> &str {
392 &self.0
393 }
394}
395
396impl FromStr for DrivePathComponent {
397 type Err = InvalidDrivePathComponent;
398
399 fn from_str(s: &str) -> Result<Self, Self::Err> {
400 if s.is_empty() {
401 return Err(InvalidDrivePathComponent::new(
402 "path component cannot be empty",
403 ));
404 }
405
406 if s.contains('\t') {
407 return Err(InvalidDrivePathComponent::new(
408 "path component cannot contain tabs",
409 ));
410 }
411
412 if s == "." || s == ".." {
413 return Err(InvalidDrivePathComponent::new(
414 "path component cannot be '.' or '..'",
415 ));
416 }
417
418 if s.contains('/') {
419 return Err(InvalidDrivePathComponent::new(
420 "path component cannot contain '/'",
421 ));
422 }
423
424 if s.len() > 256 {
425 return Err(InvalidDrivePathComponent::new("path component is too long"));
426 }
427
428 Ok(Self(s.to_string()))
429 }
430}
431
432#[derive(Debug)]
433pub struct FsError(String);
434
435impl From<String> for FsError {
436 fn from(value: String) -> Self {
437 Self(value)
438 }
439}
440
441impl From<&str> for FsError {
442 fn from(value: &str) -> Self {
443 Self(value.to_string())
444 }
445}
446
447#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
448pub enum FsNodeKind {
449 File,
450 Directory,
451}
452
453pub struct FsNode {
454 pub kind: FsNodeKind,
455 pub author: String,
456 pub lastmod: u64,
457 pub children: HashMap<DrivePathComponent, FsNodeId>,
458 pub blob: BlobId,
459 pub size: u64,
460}
461
462slotmap::new_key_type! { pub struct FsNodeId; }
463
464pub struct Fs {
465 pub root: FsNodeId,
466 pub nodes: SlotMap<FsNodeId, FsNode>,
467}
468
469impl Default for Fs {
470 fn default() -> Self {
471 let mut nodes: SlotMap<FsNodeId, FsNode> = Default::default();
472 let root = nodes.insert(FsNode {
473 kind: FsNodeKind::Directory,
474 author: "system".to_string(),
475 lastmod: 0,
476 children: Default::default(),
477 blob: Default::default(),
478 size: 0,
479 });
480 Self { root, nodes }
481 }
482}
483
484pub fn apply(fs: &mut Fs, op: &Operation) -> Result<(), FsError> {
485 match &op.data {
486 OperationData::CreateFile(data) => apply_create_file(fs, &op.header, &data),
487 OperationData::CreateDir(data) => apply_create_dir(fs, &op.header, &data),
488 OperationData::Remove(data) => apply_remove(fs, &op.header, &data),
489 OperationData::Rename(data) => apply_rename(fs, &op.header, &data),
490 }
491}
492
493pub fn apply_create_file(
494 fs: &mut Fs,
495 header: &OperationHeader,
496 data: &OperationCreateFile,
497) -> Result<(), FsError> {
498 if data.path.is_root() {
499 return Err(FsError::from("cannot create file at root"));
500 }
501
502 let mut parent_id = fs.root;
503 for comp in data.path.parent().components() {
504 let node = &mut fs.nodes[parent_id];
505 if node.kind != FsNodeKind::Directory {
506 return Err(FsError::from("cannot create file under another file"));
507 }
508 parent_id = match node.children.get(comp) {
509 Some(id) => *id,
510 None => {
511 let id = fs.nodes.insert(FsNode {
512 kind: FsNodeKind::Directory,
513 author: header.email.clone(),
514 lastmod: header.timestamp,
515 children: Default::default(),
516 blob: Default::default(),
517 size: 0,
518 });
519 fs.nodes[parent_id].children.insert(comp.clone(), id);
520 id
521 }
522 };
523 }
524
525 let name = data.path.last().unwrap();
526 let parent = &mut fs.nodes[parent_id];
527
528 if parent.kind != FsNodeKind::Directory {
529 return Err(FsError::from("cannot create file under another file"));
530 }
531
532 match parent.children.get(&name).copied() {
533 Some(node_id) => {
534 let node = &mut fs.nodes[node_id];
535 if node.kind == FsNodeKind::Directory {
536 return Err(FsError::from(
537 "node at path already exists but is a directory",
538 ));
539 }
540 node.author = header.email.clone();
541 node.lastmod = header.timestamp;
542 node.blob = data.blob.clone();
543 }
544 None => {
545 let id = fs.nodes.insert(FsNode {
546 kind: FsNodeKind::File,
547 author: header.email.clone(),
548 lastmod: header.timestamp,
549 children: Default::default(),
550 blob: data.blob.clone(),
551 size: data.size,
552 });
553 fs.nodes[parent_id].children.insert(name, id);
554 }
555 }
556 Ok(())
557}
558
559pub fn apply_create_dir(
560 fs: &mut Fs,
561 header: &OperationHeader,
562 data: &OperationCreateDir,
563) -> Result<(), FsError> {
564 let (parent, name) = data
565 .path
566 .split()
567 .ok_or_else(|| FsError::from("cannot recreate root directory"))?;
568
569 let mut parent_id = fs.root;
570 for comp in parent.components() {
571 let node = &fs.nodes[parent_id];
572 parent_id = match node.children.get(comp) {
573 Some(&child_id) => {
574 let child = &fs.nodes[child_id];
575 if child.kind == FsNodeKind::File {
576 return Err(FsError::from("cannot create directory under file"));
577 }
578 child_id
579 }
580 None => {
581 let id = fs.nodes.insert(FsNode {
582 kind: FsNodeKind::Directory,
583 author: header.email.clone(),
584 lastmod: header.timestamp,
585 children: Default::default(),
586 blob: Default::default(),
587 size: 0,
588 });
589 fs.nodes[parent_id].children.insert(comp.clone(), id);
590 id
591 }
592 };
593 }
594
595 let parent = &fs.nodes[parent_id];
596 match parent.children.get(&name).copied() {
597 Some(child_id) => {
598 let child = &fs.nodes[child_id];
599 if child.kind != FsNodeKind::Directory {
600 return Err(FsError::from(
601 "cannot create directory, the given path is already a file",
602 ));
603 }
604 }
605 None => {
606 let id = fs.nodes.insert(FsNode {
607 kind: FsNodeKind::Directory,
608 author: header.email.clone(),
609 lastmod: header.timestamp,
610 children: Default::default(),
611 blob: Default::default(),
612 size: 0,
613 });
614
615 let parent = &mut fs.nodes[parent_id];
616 parent.children.insert(name, id);
617 }
618 }
619
620 Ok(())
621}
622
623pub fn apply_remove(
624 fs: &mut Fs,
625 _header: &OperationHeader,
626 data: &OperationRemove,
627) -> Result<(), FsError> {
628 let (parent, name) = data
629 .path
630 .split()
631 .ok_or_else(|| FsError::from("cannot remove root directory"))?;
632
633 let parent_id = match find_node(fs, &parent) {
634 Some(id) => id,
635 None => return Ok(()),
636 };
637
638 let parent = &mut fs.nodes[parent_id];
639 parent.children.remove(&name);
640
641 Ok(())
642}
643
644fn find_node(fs: &Fs, path: &DrivePath) -> Option<FsNodeId> {
645 let mut node_id = fs.root;
646 for comp in path.components() {
647 let node = &fs.nodes[node_id];
648 node_id = *node.children.get(comp)?;
649 }
650 Some(node_id)
651}
652
653pub fn apply_rename(
654 fs: &mut Fs,
655 _header: &OperationHeader,
656 data: &OperationRename,
657) -> Result<(), FsError> {
658 let (old_parent, old_name) = data
659 .old
660 .split()
661 .ok_or_else(|| FsError::from("cannot move root directory"))?;
662
663 let (new_parent, new_name) = data
664 .new
665 .split()
666 .ok_or_else(|| FsError::from("move destination cannot be root directory"))?;
667
668 let old_parent_id = find_node(fs, &old_parent).unwrap();
669 let old_parent = &mut fs.nodes[old_parent_id];
670 let node_id = old_parent.children.remove(&old_name).unwrap();
671
672 let new_parent_id = find_node(fs, &new_parent).unwrap();
673 let new_parent = &mut fs.nodes[new_parent_id];
674 new_parent.children.insert(new_name, node_id);
675
676 Ok(())
677}
678
679#[derive(Debug, Parser)]
680struct Cli {
681 #[clap(subcommand)]
682 cmd: Cmd,
683}
684
685#[derive(Debug, Parser)]
686struct CliCommon {
687 #[clap(long, default_value = "./", env = "FCTDRIVE_PATH")]
688 drive: PathBuf,
689}
690
691#[derive(Debug, Subcommand)]
692enum Cmd {
693 CreateFile(CreateFileArgs),
694 CreateDir(CreateDirArgs),
695 Remove(RemoveArgs),
696 Rename(RenameArgs),
697 Ls(LsArgs),
698 Import(ImportArgs),
699 Blob(BlobArgs),
700}
701
702#[derive(Debug, Args)]
703struct CreateFileArgs {
704 #[clap(flatten)]
705 common: CliCommon,
706
707 #[clap(long)]
708 timestamp: Option<u64>,
709
710 #[clap(long)]
711 email: String,
712
713 #[clap(long)]
714 path: DrivePath,
715
716 #[clap(long)]
717 file: std::path::PathBuf,
718}
719
720#[derive(Debug, Args)]
721struct CreateDirArgs {
722 #[clap(flatten)]
723 common: CliCommon,
724
725 #[clap(long)]
726 timestamp: Option<u64>,
727
728 #[clap(long)]
729 email: String,
730
731 #[clap(long)]
732 path: DrivePath,
733}
734
735#[derive(Debug, Args)]
736struct RemoveArgs {
737 #[clap(flatten)]
738 common: CliCommon,
739
740 #[clap(long)]
741 timestamp: Option<u64>,
742
743 #[clap(long)]
744 email: String,
745
746 #[clap(long)]
747 path: DrivePath,
748}
749
750#[derive(Debug, Args)]
751struct RenameArgs {
752 #[clap(flatten)]
753 common: CliCommon,
754
755 #[clap(long)]
756 timestamp: Option<u64>,
757
758 #[clap(long)]
759 email: String,
760
761 #[clap(long)]
762 old: DrivePath,
763
764 #[clap(long)]
765 new: DrivePath,
766}
767
768#[derive(Debug, Args)]
769struct LsArgs {
770 #[clap(flatten)]
771 common: CliCommon,
772
773 #[clap(short, long)]
774 recursive: bool,
775
776 path: Option<DrivePath>,
777}
778
779#[derive(Debug, Args)]
780struct ImportArgs {
781 #[clap(flatten)]
782 common: CliCommon,
783
784 #[clap(long)]
785 timestamp: Option<u64>,
786
787 #[clap(long)]
788 email: String,
789
790 path: PathBuf,
791}
792
793#[derive(Debug, Args)]
794struct BlobArgs {
795 #[clap(flatten)]
796 common: CliCommon,
797
798 blob_id: BlobId,
799}
800
801fn main() {
802 let cli = Cli::parse();
803
804 match cli.cmd {
805 Cmd::CreateFile(args) => cmd_create_file(args),
806 Cmd::CreateDir(args) => cmd_create_dir(args),
807 Cmd::Remove(args) => cmd_remove(args),
808 Cmd::Rename(args) => cmd_rename(args),
809 Cmd::Ls(args) => cmd_ls(args),
810 Cmd::Import(args) => cmd_import(args),
811 Cmd::Blob(args) => cmd_blob(args),
812 }
813}
814
815fn cmd_create_file(args: CreateFileArgs) {
816 let store = common_create_blob_store(&args.common);
817 let file_blob_id = blob_hash_file(&args.file).unwrap();
818 let file_blob_size = blob_size(&store, &file_blob_id).unwrap();
819
820 let _lock = common_write_lock(&args.common);
821 let mut fs = Fs::default();
822 let mut ops = common_read_log_file(&args.common);
823 ops.iter().for_each(|op| apply(&mut fs, op).unwrap());
824
825 let timestamp = args.timestamp.unwrap_or_else(get_timestamp);
826 let revision = get_next_revision(&ops);
827 blob_import_file(&store, ImportMode::HardLink, &file_blob_id, &args.file).unwrap();
828
829 let new_op = Operation {
830 header: OperationHeader {
831 timestamp,
832 revision,
833 email: args.email,
834 },
835 data: OperationData::CreateFile(OperationCreateFile {
836 path: args.path,
837 blob: file_blob_id,
838 size: file_blob_size,
839 }),
840 };
841 apply(&mut fs, &new_op).unwrap();
842 ops.push(new_op);
843 common_write_log_file(&args.common, &ops);
844}
845
846fn cmd_create_dir(args: CreateDirArgs) {
847 let _lock = common_write_lock(&args.common);
848 let mut fs = Fs::default();
849 let mut ops = common_read_log_file(&args.common);
850 ops.iter().for_each(|op| apply(&mut fs, op).unwrap());
851
852 let timestamp = args.timestamp.unwrap_or_else(get_timestamp);
853 let revision = get_next_revision(&ops);
854
855 let new_op = Operation {
856 header: OperationHeader {
857 timestamp,
858 revision,
859 email: args.email,
860 },
861 data: OperationData::CreateDir(OperationCreateDir { path: args.path }),
862 };
863 apply(&mut fs, &new_op).unwrap();
864 ops.push(new_op);
865 common_write_log_file(&args.common, &ops);
866}
867
868fn cmd_remove(args: RemoveArgs) {
869 let _lock = common_write_lock(&args.common);
870 let mut fs = Fs::default();
871 let mut ops = common_read_log_file(&args.common);
872 ops.iter().for_each(|op| apply(&mut fs, op).unwrap());
873
874 let timestamp = args.timestamp.unwrap_or_else(get_timestamp);
875 let revision = get_next_revision(&ops);
876
877 let new_op = Operation {
878 header: OperationHeader {
879 timestamp,
880 revision,
881 email: args.email,
882 },
883 data: OperationData::Remove(OperationRemove { path: args.path }),
884 };
885 apply(&mut fs, &new_op).unwrap();
886 ops.push(new_op);
887 common_write_log_file(&args.common, &ops);
888}
889
890fn cmd_rename(args: RenameArgs) {
891 let _lock = common_write_lock(&args.common);
892 let mut fs = Fs::default();
893 let mut ops = common_read_log_file(&args.common);
894 ops.iter().for_each(|op| apply(&mut fs, op).unwrap());
895
896 let timestamp = args.timestamp.unwrap_or_else(get_timestamp);
897 let revision = get_next_revision(&ops);
898
899 let new_op = Operation {
900 header: OperationHeader {
901 timestamp,
902 revision,
903 email: args.email,
904 },
905 data: OperationData::Rename(OperationRename {
906 old: args.old,
907 new: args.new,
908 }),
909 };
910
911 apply(&mut fs, &new_op).unwrap();
912 ops.push(new_op);
913 common_write_log_file(&args.common, &ops);
914}
915
916type FsNodeWriter<'a> = std::io::BufWriter<std::io::StdoutLock<'a>>;
917
918fn cmd_ls(args: LsArgs) {
919 let stdout = std::io::stdout().lock();
920 let mut writer = BufWriter::new(stdout);
921 let mut fs = Fs::default();
922 let ops = common_read_log_file(&args.common);
923 ops.iter().for_each(|op| apply(&mut fs, op).unwrap());
924 let node_id = match args.path {
925 Some(path) => find_node(&fs, &path),
926 None => Some(fs.root),
927 };
928 if let Some(node_id) = node_id {
929 write_node(
930 &mut writer,
931 &fs,
932 node_id,
933 Default::default(),
934 args.recursive,
935 true,
936 );
937 }
938 writer.flush().unwrap();
939 std::process::exit(0);
940}
941
942fn write_node(
943 writer: &mut FsNodeWriter,
944 fs: &Fs,
945 node_id: FsNodeId,
946 path: DrivePath,
947 recursive: bool,
948 recurse: bool,
949) {
950 let node = &fs.nodes[node_id];
951 match node.kind {
952 FsNodeKind::File => {
953 writeln!(
954 writer,
955 "{}\tfile\t{}\t{}\t{}\t{}",
956 path, node.lastmod, node.blob, node.size, node.author
957 )
958 .unwrap();
959 }
960 FsNodeKind::Directory => {
961 writeln!(
962 writer,
963 "{}\tdir\t{}\t-\t-\t{}",
964 path, node.lastmod, node.author
965 )
966 .unwrap();
967 if recursive || recurse {
968 for (child_comp, child_id) in node.children.iter() {
969 let child_path = path.push(child_comp.clone());
970 write_node(writer, fs, *child_id, child_path, recursive, false);
971 }
972 }
973 }
974 }
975}
976
977#[derive(Debug, Clone)]
978struct Queue<T>(Arc<Mutex<VecDeque<T>>>);
979
980impl<T> Default for Queue<T> {
981 fn default() -> Self {
982 Self(Arc::new(Mutex::new(Default::default())))
983 }
984}
985
986impl<T> From<Vec<T>> for Queue<T> {
987 fn from(value: Vec<T>) -> Self {
988 Self(Arc::new(Mutex::new(value.into())))
989 }
990}
991
992impl<T> Queue<T> {
993 pub fn push(&self, v: T) {
994 self.0.lock().unwrap().push_back(v);
995 }
996
997 pub fn pop(&self) -> Option<T> {
998 self.0.lock().unwrap().pop_front()
999 }
1000}
1001
1002fn cmd_import(args: ImportArgs) {
1003 let _lock = common_write_lock(&args.common);
1004
1005 let mut ops = common_read_log_file(&args.common);
1006
1007 let store = BlobStore::new("blobs");
1008 let timestamp = args.timestamp.unwrap_or_else(get_timestamp);
1009 let root = args.path.canonicalize().unwrap();
1010
1011 let files = Queue::from(collect_all_file_paths(&root));
1012 let num_threads = std::thread::available_parallelism().unwrap().get();
1013 let mut handles = Vec::default();
1014 for _ in 0..num_threads {
1015 let root = root.clone();
1016 let email = args.email.clone();
1017 let files = files.clone();
1018 let store = store.clone();
1019 let handle = std::thread::spawn(move || {
1020 let mut ops = Vec::default();
1021 while let Some(file) = files.pop() {
1022 let rel = file.strip_prefix(&root).unwrap();
1023 let drive_path = rel.to_str().unwrap().parse::<DrivePath>().unwrap();
1024 let blob_id = blob_hash_file(&file).unwrap();
1025 blob_import_file(&store, ImportMode::HardLink, &blob_id, &file).unwrap();
1026 let blob_size = blob_size(&store, &blob_id).unwrap();
1027 let op = Operation {
1028 header: OperationHeader {
1029 timestamp,
1030 revision: 0,
1031 email: email.clone(),
1032 },
1033 data: OperationData::CreateFile(OperationCreateFile {
1034 path: drive_path,
1035 blob: blob_id,
1036 size: blob_size,
1037 }),
1038 };
1039 ops.push(op);
1040 }
1041 ops
1042 });
1043 handles.push(handle);
1044 }
1045
1046 let mut fs = Fs::default();
1047 let mut next_rev = get_next_revision(&ops);
1048 for handle in handles {
1049 let mut task_ops = handle.join().unwrap();
1050 for op in &mut task_ops {
1051 op.header.revision = next_rev;
1052 next_rev += 1;
1053 }
1054 ops.extend(task_ops);
1055 }
1056 ops.iter().for_each(|op| apply(&mut fs, op).unwrap());
1057 common_write_log_file(&args.common, &ops);
1058}
1059
1060fn cmd_blob(args: BlobArgs) {
1061 let store = common_create_blob_store(&args.common);
1062 let path = blob_path(&store, &args.blob_id);
1063 println!("{}", path.display());
1064}
1065
1066fn collect_all_file_paths(root: &Path) -> Vec<PathBuf> {
1067 let mut queue = vec![root.to_path_buf()];
1068 let mut files = vec![];
1069 while let Some(path) = queue.pop() {
1070 if path.is_dir() {
1071 let mut read = path.read_dir().unwrap();
1072 while let Some(entry) = read.next() {
1073 let entry = entry.unwrap();
1074 queue.push(entry.path());
1075 }
1076 } else {
1077 files.push(path);
1078 }
1079 }
1080 files
1081}
1082
1083fn common_create_blob_store(common: &CliCommon) -> BlobStore {
1084 BlobStore::new(common.drive.join("blobs"))
1085}
1086
1087fn common_log_file_path(common: &CliCommon) -> PathBuf {
1088 common.drive.join("log.txt")
1089}
1090
1091fn common_log_temp_file_path(common: &CliCommon) -> PathBuf {
1092 common.drive.join("log.txt.tmp")
1093}
1094
1095fn common_read_log_file(common: &CliCommon) -> Vec<Operation> {
1096 let log_file_path = common_log_file_path(common);
1097 let mut operations = Vec::default();
1098 if std::fs::exists(&log_file_path).unwrap() {
1099 let contents = std::fs::read_to_string(&log_file_path).unwrap();
1100 for line in contents.lines() {
1101 let operation = line.parse().unwrap();
1102 operations.push(operation);
1103 }
1104 }
1105 operations
1106}
1107
1108fn common_write_log_file(common: &CliCommon, ops: &[Operation]) {
1109 let log_file_path = common_log_file_path(common);
1110 let log_temp_file_path = common_log_temp_file_path(common);
1111 {
1112 let file = File::options()
1113 .create(true)
1114 .write(true)
1115 .truncate(true)
1116 .open(&log_temp_file_path)
1117 .unwrap();
1118 let mut writer = BufWriter::new(file);
1119 for op in ops {
1120 writeln!(writer, "{op}").unwrap();
1121 }
1122 writer.flush().unwrap();
1123 }
1124 std::fs::rename(&log_temp_file_path, &log_file_path).unwrap();
1125}
1126
1127fn common_write_lock_path(common: &CliCommon) -> PathBuf {
1128 common.drive.join("write.lock")
1129}
1130
1131fn common_write_lock(common: &CliCommon) -> File {
1132 let lock_path = common_write_lock_path(common);
1133 let file = std::fs::OpenOptions::new()
1134 .write(true)
1135 .create(true)
1136 .open(lock_path)
1137 .unwrap();
1138 file.lock().unwrap();
1139 file
1140}
1141
1142fn get_timestamp() -> u64 {
1143 SystemTime::now()
1144 .duration_since(SystemTime::UNIX_EPOCH)
1145 .unwrap()
1146 .as_secs()
1147}
1148
1149fn get_next_revision(ops: &[Operation]) -> u64 {
1150 match ops.last() {
1151 Some(op) => op.header.revision + 1,
1152 None => 0,
1153 }
1154}