Skip to content

Commit

Permalink
refactor(cli): breaking up acc-packet logic within its own mod
Browse files Browse the repository at this point in the history
  • Loading branch information
bochaco committed Mar 13, 2024
1 parent cdc574b commit 300459c
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 142 deletions.
154 changes: 12 additions & 142 deletions sn_cli/src/subcommands/acc_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use super::files::ChunkManager;
mod change_tracking;

use change_tracking::*;

use serde::{Deserialize, Serialize};
use sn_client::protocol::storage::{Chunk, RegisterAddress, RetryStrategy};
use sn_client::registers::EntryHash;
use sn_client::transfers::HotWallet;
use sn_client::{Client, FilesApi, FolderEntry, FoldersApi, Metadata, WalletClient};
use super::files::ChunkManager;

use crate::subcommands::files::{
self, download::download_file, iterative_uploader::IterativeUploader,
upload::FilesUploadOptions,
};

use sn_client::{
protocol::storage::{Chunk, RegisterAddress, RetryStrategy},
registers::EntryHash,
transfers::HotWallet,
Client, FilesApi, FolderEntry, FoldersApi, Metadata, WalletClient,
};

use color_eyre::{
eyre::{bail, eyre},
Result,
Expand All @@ -28,7 +34,6 @@ use std::{
BTreeMap,
},
ffi::OsString,
fmt,
fs::{create_dir_all, remove_dir_all, remove_file, File},
io::Write,
path::{Path, PathBuf},
Expand All @@ -37,87 +42,6 @@ use tokio::task::JoinSet;
use walkdir::{DirEntry, WalkDir};
use xor_name::XorName;

// Name of hidden folder where tracking information and metadata is locally kept.
const SAFE_TRACKING_CHANGES_DIR: &str = ".safe";

// Subfolder where files metadata will be cached
const METADATA_CACHE_DIR: &str = "metadata";

// Name of the file where metadata about root folder is locally kept.
const ROOT_FOLDER_METADATA_FILENAME: &str = "root_folder.addr";

// Container to keep track in memory what changes are detected in local Folders hierarchy and files.
type Folders = BTreeMap<PathBuf, (FoldersApi, FolderChange)>;

// Type of local changes detected to a Folder
#[derive(Clone, Debug, PartialEq)]
enum FolderChange {
NoChange,
NewFolder,
NewEntries,
}

impl FolderChange {
/// Returns true if it's currently set to NewFolder.
pub fn is_new_folder(&self) -> bool {
self == &Self::NewFolder
}

/// If it's currently set to NoChange then switch it to NewEntries.
/// Otherwise we don't need to change it as the entire Folder will need to be uploaded.
pub fn has_new_entries(&mut self) {
if self == &Self::NoChange {
*self = Self::NewEntries;
}
}
}

// Changes detected locally which eventually can be applied and upload to network.
#[derive(Default)]
struct ChangesToApply {
folders: Folders,
mutations: Vec<Mutation>,
}

// Type of mutation detected locally.
#[derive(Debug)]
enum Mutation {
NewFile(MetadataTrackingInfo),
FileRemoved((PathBuf, XorName)),
FileContentChanged((XorName, MetadataTrackingInfo)),
NewFolder(MetadataTrackingInfo),
FolderRemoved((PathBuf, XorName)),
}

impl fmt::Display for Mutation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::NewFile(tracking_info) => {
write!(f, "New file: {:?}", tracking_info.file_path)
}
Self::FileRemoved((path, _)) => write!(f, "File removed: {path:?}"),
Self::FileContentChanged((_, tracking_info)) => {
write!(f, "File content changed: {:?}", tracking_info.file_path)
}
Self::NewFolder(tracking_info) => {
write!(f, "New folder: {:?}", tracking_info.file_path)
}
Self::FolderRemoved((path, _)) => write!(f, "Folder removed: {path:?}"),
}
}
}

// Information stored locally to keep track of local changes to files/folders.
// TODO: to make file changes discovery more efficient, and prevent chunking for
// such purposes, add more info like file size and last modified timestamp.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct MetadataTrackingInfo {
file_path: PathBuf,
meta_xorname: XorName,
metadata: Metadata,
entry_hash: EntryHash,
}

/// Object which allows a user to store and manage files, wallets, etc., with the ability
/// and tools necessary to keep a local instance in sync with its remote version stored on the network.
/// TODO: currently only files and folders are supported, wallets, keys, etc., to be added later.
Expand Down Expand Up @@ -847,60 +771,6 @@ impl AccountPacket {
}
}

// Build absolute paths for the different dirs to be used for locally tracking changes
fn build_tracking_info_paths(path: &Path) -> Result<(PathBuf, PathBuf, PathBuf)> {
let files_dir = path.to_path_buf().canonicalize()?;
let tracking_info_dir = files_dir.join(SAFE_TRACKING_CHANGES_DIR);
let meta_dir = tracking_info_dir.join(METADATA_CACHE_DIR);
create_dir_all(&meta_dir)
.map_err(|err| eyre!("The path provided needs to be a directory: {err}"))?;

Ok((files_dir, tracking_info_dir, meta_dir))
}

fn read_tracking_info_from_disk(
meta_dir: &Path,
) -> Result<BTreeMap<PathBuf, MetadataTrackingInfo>> {
let mut curr_tracking_info = BTreeMap::new();
for entry in WalkDir::new(meta_dir)
.into_iter()
.flatten()
.filter(|e| e.file_type().is_file() && e.file_name() != ROOT_FOLDER_METADATA_FILENAME)
{
let path = entry.path();
let bytes = std::fs::read(path)
.map_err(|err| eyre!("Error while reading the tracking info from {path:?}: {err}"))?;
let tracking_info: MetadataTrackingInfo = rmp_serde::from_slice(&bytes)
.map_err(|err| eyre!("Error while deserializing tracking info from {path:?}: {err}"))?;

curr_tracking_info.insert(tracking_info.file_path.clone(), tracking_info);
}

Ok(curr_tracking_info)
}

// Store tracking info about the root folder in a file to keep track of any changes made
fn store_root_folder_tracking_info(
meta_dir: &Path,
root_folder_addr: RegisterAddress,
created: bool,
) -> Result<()> {
let path = meta_dir.join(ROOT_FOLDER_METADATA_FILENAME);
let mut meta_file = File::create(path)?;
meta_file.write_all(&rmp_serde::to_vec(&(root_folder_addr, created))?)?;

Ok(())
}

// Read the tracking info about the root folder
fn read_root_folder_addr(meta_dir: &Path) -> Result<(RegisterAddress, bool)> {
let path = meta_dir.join(ROOT_FOLDER_METADATA_FILENAME);
let bytes = std::fs::read(&path)
.map_err(|err| eyre!("Error while reading the tracking info from {path:?}: {err:?}"))?;

Ok(rmp_serde::from_slice(&bytes)?)
}

// Given an absolute path, find the Folder containing such item, and remove it from its entries.
fn remove_from_parent(folders: &mut Folders, path: &Path, entry_hash: EntryHash) -> Result<()> {
if let Some((parent_folder, folder_change)) = path.parent().and_then(|p| folders.get_mut(p)) {
Expand Down
156 changes: 156 additions & 0 deletions sn_cli/src/subcommands/acc_packet/change_tracking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use sn_client::{protocol::storage::RegisterAddress, registers::EntryHash, FoldersApi, Metadata};

use color_eyre::{eyre::eyre, Result};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
fmt,
fs::{create_dir_all, File},
io::Write,
path::{Path, PathBuf},
};
use walkdir::WalkDir;
use xor_name::XorName;

// Name of hidden folder where tracking information and metadata is locally kept.
pub(super) const SAFE_TRACKING_CHANGES_DIR: &str = ".safe";

// Subfolder where files metadata will be cached
pub(super) const METADATA_CACHE_DIR: &str = "metadata";

// Name of the file where metadata about root folder is locally kept.
pub(super) const ROOT_FOLDER_METADATA_FILENAME: &str = "root_folder.addr";

// Container to keep track in memory what changes are detected in local Folders hierarchy and files.
pub(super) type Folders = BTreeMap<PathBuf, (FoldersApi, FolderChange)>;

// Type of local changes detected to a Folder
#[derive(Clone, Debug, PartialEq)]
pub(super) enum FolderChange {
NoChange,
NewFolder,
NewEntries,
}

impl FolderChange {
/// Returns true if it's currently set to NewFolder.
pub fn is_new_folder(&self) -> bool {
self == &Self::NewFolder
}

/// If it's currently set to NoChange then switch it to NewEntries.
/// Otherwise we don't need to change it as the entire Folder will need to be uploaded.
pub fn has_new_entries(&mut self) {
if self == &Self::NoChange {
*self = Self::NewEntries;
}
}
}

// Changes detected locally which eventually can be applied and upload to network.
#[derive(Default)]
pub(super) struct ChangesToApply {
pub folders: Folders,
pub mutations: Vec<Mutation>,
}

// Type of mutation detected locally.
#[derive(Debug)]
pub(super) enum Mutation {
NewFile(MetadataTrackingInfo),
FileRemoved((PathBuf, XorName)),
FileContentChanged((XorName, MetadataTrackingInfo)),
NewFolder(MetadataTrackingInfo),
FolderRemoved((PathBuf, XorName)),
}

impl fmt::Display for Mutation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::NewFile(tracking_info) => {
write!(f, "New file: {:?}", tracking_info.file_path)
}
Self::FileRemoved((path, _)) => write!(f, "File removed: {path:?}"),
Self::FileContentChanged((_, tracking_info)) => {
write!(f, "File content changed: {:?}", tracking_info.file_path)
}
Self::NewFolder(tracking_info) => {
write!(f, "New folder: {:?}", tracking_info.file_path)
}
Self::FolderRemoved((path, _)) => write!(f, "Folder removed: {path:?}"),
}
}
}

// Information stored locally to keep track of local changes to files/folders.
// TODO: to make file changes discovery more efficient, and prevent chunking for

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
// such purposes, add more info like file size and last modified timestamp.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub(super) struct MetadataTrackingInfo {
pub file_path: PathBuf,
pub meta_xorname: XorName,
pub metadata: Metadata,
pub entry_hash: EntryHash,
}

// Build absolute paths for the different dirs to be used for locally tracking changes
pub(super) fn build_tracking_info_paths(path: &Path) -> Result<(PathBuf, PathBuf, PathBuf)> {
let files_dir = path.to_path_buf().canonicalize()?;
let tracking_info_dir = files_dir.join(SAFE_TRACKING_CHANGES_DIR);
let meta_dir = tracking_info_dir.join(METADATA_CACHE_DIR);
create_dir_all(&meta_dir)
.map_err(|err| eyre!("The path provided needs to be a directory: {err}"))?;

Ok((files_dir, tracking_info_dir, meta_dir))
}

pub(super) fn read_tracking_info_from_disk(
meta_dir: &Path,
) -> Result<BTreeMap<PathBuf, MetadataTrackingInfo>> {
let mut curr_tracking_info = BTreeMap::new();
for entry in WalkDir::new(meta_dir)
.into_iter()
.flatten()
.filter(|e| e.file_type().is_file() && e.file_name() != ROOT_FOLDER_METADATA_FILENAME)
{
let path = entry.path();
let bytes = std::fs::read(path)
.map_err(|err| eyre!("Error while reading the tracking info from {path:?}: {err}"))?;
let tracking_info: MetadataTrackingInfo = rmp_serde::from_slice(&bytes)
.map_err(|err| eyre!("Error while deserializing tracking info from {path:?}: {err}"))?;

curr_tracking_info.insert(tracking_info.file_path.clone(), tracking_info);
}

Ok(curr_tracking_info)
}

// Store tracking info about the root folder in a file to keep track of any changes made
pub(super) fn store_root_folder_tracking_info(
meta_dir: &Path,
root_folder_addr: RegisterAddress,
created: bool,
) -> Result<()> {
let path = meta_dir.join(ROOT_FOLDER_METADATA_FILENAME);
let mut meta_file = File::create(path)?;
meta_file.write_all(&rmp_serde::to_vec(&(root_folder_addr, created))?)?;

Ok(())
}

// Read the tracking info about the root folder
pub(super) fn read_root_folder_addr(meta_dir: &Path) -> Result<(RegisterAddress, bool)> {
let path = meta_dir.join(ROOT_FOLDER_METADATA_FILENAME);
let bytes = std::fs::read(&path)
.map_err(|err| eyre!("Error while reading the tracking info from {path:?}: {err:?}"))?;

Ok(rmp_serde::from_slice(&bytes)?)
}

0 comments on commit 300459c

Please sign in to comment.