From 86ba1bd04a9ab08c66d622d582fccf15fbb4a40b Mon Sep 17 00:00:00 2001 From: Ericson Fogo Soares Date: Tue, 28 Jun 2022 22:56:49 -0300 Subject: [PATCH 01/51] Fixing Clippy warnings * Using tokio on all filesystem operations * Some minor tweaks to be more consistent on paths between &str, AsRef and PathBuf * Using logging instead of println --- Cargo.lock | Bin 167334 -> 167624 bytes core/Cargo.toml | 2 +- core/src/encode/metadata.rs | 7 +- core/src/encode/thumb.rs | 70 +++++---- core/src/file/cas/checksum.rs | 37 ++--- core/src/file/cas/identifier.rs | 44 +++--- core/src/file/explorer/open.rs | 40 +++--- core/src/file/indexer/mod.rs | 12 +- core/src/file/indexer/scan.rs | 65 +++++---- core/src/file/mod.rs | 83 ++++++----- core/src/job/jobs.rs | 41 +++--- core/src/job/worker.rs | 9 +- core/src/lib.rs | 65 +++++---- core/src/library/loader.rs | 45 +++--- core/src/library/statistics.rs | 71 ++++----- core/src/node/mod.rs | 57 ++++---- core/src/node/state.rs | 64 +++++---- core/src/sys/locations.rs | 245 ++++++++++++++++---------------- core/src/sys/mod.rs | 8 +- core/src/sys/volumes.rs | 21 +-- core/src/util/db.rs | 30 ++-- 21 files changed, 518 insertions(+), 498 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca71bf05e6c80fe1745988f1e5a2a2837b8930c0..d0af4fd42471cf581af24e09047db2aa2e810e8a 100644 GIT binary patch delta 150 zcmZ2Bo9o0}u7)j)vyM&w%*`aSecds}#3nHXrQ+10lvE`hE(N91(##ay+{EOf{OSKo z8I`$^__}3`64S#xn1bw0O)S#P4J`~4O)V`AQqvMm4UCM<6O)q6QxmuuN println!("error: {}", error), +// Err(error) => error!("error: {}", error), // } // Ok(()) // } diff --git a/core/src/encode/thumb.rs b/core/src/encode/thumb.rs index bb148d46b..d9d23d587 100644 --- a/core/src/encode/thumb.rs +++ b/core/src/encode/thumb.rs @@ -1,22 +1,21 @@ -use crate::job::JobReportUpdate; -use crate::node::get_nodestate; use crate::{ - job::{Job, WorkerContext}, + job::{Job, JobReportUpdate, WorkerContext}, + node::get_nodestate, prisma::file_path, - CoreContext, + sys, CoreContext, CoreEvent, }; -use crate::{sys, CoreEvent}; use futures::executor::block_on; -use image::*; +use image::{self, imageops, DynamicImage, GenericImageView}; use log::{error, info}; -use std::fs; +use std::error::Error; use std::path::{Path, PathBuf}; -use webp::*; +use tokio::fs; +use webp::Encoder; #[derive(Debug, Clone)] pub struct ThumbnailJob { pub location_id: i32, - pub path: String, + pub path: PathBuf, pub background: bool, } @@ -29,30 +28,34 @@ impl Job for ThumbnailJob { fn name(&self) -> &'static str { "thumbnailer" } - async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { - let config = get_nodestate(); - let core_ctx = ctx.core_ctx.clone(); - let location = sys::get_location(&core_ctx, self.location_id).await?; + async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { + let config = get_nodestate(); + + let location = sys::get_location(&ctx.core_ctx, self.location_id).await?; info!( - "Searching for images in location {} at path {}", + "Searching for images in location {} at path {:#?}", location.id, self.path ); // create all necessary directories if they don't exist fs::create_dir_all( - Path::new(&config.data_path) + config + .data_path + .as_ref() + .unwrap() .join(THUMBNAIL_CACHE_DIR_NAME) .join(format!("{}", self.location_id)), - )?; + ) + .await?; let root_path = location.path.unwrap(); // query database for all files in this location that need thumbnails - let image_files = get_images(&core_ctx, self.location_id, &self.path).await?; + let image_files = get_images(&ctx.core_ctx, self.location_id, &self.path).await?; info!("Found {:?} files", image_files.len()); - let is_background = self.background.clone(); + let is_background = self.background; tokio::task::spawn_blocking(move || { ctx.progress(vec![ @@ -89,7 +92,10 @@ impl Job for ThumbnailJob { }; // Define and write the WebP-encoded file to a given path - let output_path = Path::new(&config.data_path) + let output_path = config + .data_path + .as_ref() + .unwrap() .join(THUMBNAIL_CACHE_DIR_NAME) .join(format!("{}", location.id)) .join(&cas_id) @@ -98,7 +104,7 @@ impl Job for ThumbnailJob { // check if file exists at output path if !output_path.exists() { info!("Writing {:?} to {:?}", path, output_path); - generate_thumbnail(&path, &output_path) + block_on(generate_thumbnail(&path, &output_path)) .map_err(|e| { info!("Error generating thumb {:?}", e); }) @@ -120,27 +126,27 @@ impl Job for ThumbnailJob { } } -pub fn generate_thumbnail( - file_path: &PathBuf, - output_path: &PathBuf, -) -> Result<(), Box> { +pub async fn generate_thumbnail>( + file_path: P, + output_path: P, +) -> Result<(), Box> { // Using `image` crate, open the included .jpg file let img = image::open(file_path)?; let (w, h) = img.dimensions(); // Optionally, resize the existing photo and convert back into DynamicImage - let img: DynamicImage = image::DynamicImage::ImageRgba8(imageops::resize( + let img = DynamicImage::ImageRgba8(imageops::resize( &img, (w as f32 * THUMBNAIL_SIZE_FACTOR) as u32, (h as f32 * THUMBNAIL_SIZE_FACTOR) as u32, imageops::FilterType::Triangle, )); // Create the WebP encoder for the above image - let encoder: Encoder = Encoder::from_image(&img)?; + let encoder = Encoder::from_image(&img)?; // Encode the image at a specified quality 0-100 - let webp: WebPMemory = encoder.encode(THUMBNAIL_QUALITY); + let webp = encoder.encode(THUMBNAIL_QUALITY); - std::fs::write(&output_path, &*webp)?; + fs::write(output_path, &*webp).await?; Ok(()) } @@ -148,7 +154,7 @@ pub fn generate_thumbnail( pub async fn get_images( ctx: &CoreContext, location_id: i32, - path: &str, + path: impl AsRef, ) -> Result, std::io::Error> { let mut params = vec![ file_path::location_id::equals(Some(location_id)), @@ -161,8 +167,10 @@ pub async fn get_images( ]), ]; - if !path.is_empty() { - params.push(file_path::materialized_path::starts_with(path.to_string())) + let path_str = path.as_ref().to_string_lossy().to_string(); + + if !path_str.is_empty() { + params.push(file_path::materialized_path::starts_with(path_str)) } let image_files = ctx diff --git a/core/src/file/cas/checksum.rs b/core/src/file/cas/checksum.rs index 78bd09aca..08c12675c 100644 --- a/core/src/file/cas/checksum.rs +++ b/core/src/file/cas/checksum.rs @@ -1,36 +1,27 @@ use data_encoding::HEXLOWER; use ring::digest::{Context, SHA256}; -use std::convert::TryInto; -use std::fs::File; - -use std::io; use std::path::PathBuf; +use tokio::{ + fs::File, + io::{self, AsyncReadExt, AsyncSeekExt, SeekFrom}, +}; static SAMPLE_COUNT: u64 = 4; static SAMPLE_SIZE: u64 = 10000; -fn read_at(file: &File, offset: u64, size: u64) -> Result, io::Error> { +async fn read_at(file: &mut File, offset: u64, size: u64) -> Result, io::Error> { let mut buf = vec![0u8; size as usize]; - #[cfg(target_family = "unix")] - { - use std::os::unix::prelude::*; - file.read_exact_at(&mut buf, offset)?; - } - - #[cfg(target_family = "windows")] - { - use std::os::windows::prelude::*; - file.seek_read(&mut buf, offset)?; - } + file.seek(SeekFrom::Start(offset)).await?; + file.read_exact(&mut buf).await?; Ok(buf) } -pub fn generate_cas_id(path: PathBuf, size: u64) -> Result { +pub async fn generate_cas_id(path: PathBuf, size: u64) -> Result { // open file reference - let file = File::open(path)?; + let mut file = File::open(path).await?; let mut context = Context::new(&SHA256); @@ -39,20 +30,16 @@ pub fn generate_cas_id(path: PathBuf, size: u64) -> Result { // if size is small enough, just read the whole thing if SAMPLE_COUNT * SAMPLE_SIZE > size { - let buf = read_at(&file, 0, size.try_into().unwrap())?; + let buf = read_at(&mut file, 0, size).await?; context.update(&buf); } else { // loop over samples for i in 0..SAMPLE_COUNT { - let buf = read_at( - &file, - (size / SAMPLE_COUNT) * i, - SAMPLE_SIZE.try_into().unwrap(), - )?; + let buf = read_at(&mut file, (size / SAMPLE_COUNT) * i, SAMPLE_SIZE).await?; context.update(&buf); } // sample end of file - let buf = read_at(&file, size - SAMPLE_SIZE, SAMPLE_SIZE.try_into().unwrap())?; + let buf = read_at(&mut file, size - SAMPLE_SIZE, SAMPLE_SIZE).await?; context.update(&buf); } diff --git a/core/src/file/cas/identifier.rs b/core/src/file/cas/identifier.rs index 94e5c5aaf..de9a54483 100644 --- a/core/src/file/cas/identifier.rs +++ b/core/src/file/cas/identifier.rs @@ -13,8 +13,9 @@ use log::info; use prisma_client_rust::{prisma_models::PrismaValue, raw, raw::Raw, Direction}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::path::Path; -use std::{fs, io}; +use std::error::Error; +use std::path::{Path, PathBuf}; +use tokio::{fs, io}; // FileIdentifierJob takes file_paths without a file_id and uniquely identifies them // first: generating the cas_id and extracting metadata @@ -22,7 +23,7 @@ use std::{fs, io}; #[derive(Debug)] pub struct FileIdentifierJob { pub location_id: i32, - pub path: String, + pub path: PathBuf, } // we break this job into chunks of 100 to improve performance @@ -33,11 +34,12 @@ impl Job for FileIdentifierJob { fn name(&self) -> &'static str { "file_identifier" } - async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { + + async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { info!("Identifying orphan file paths..."); let location = get_location(&ctx.core_ctx, self.location_id).await?; - let location_path = location.path.unwrap_or("".to_string()); + let location_path = location.path.unwrap_or_else(|| "".to_string()); let total_count = count_orphan_file_paths(&ctx.core_ctx, location.id.into()).await?; info!("Found {} orphan file paths", total_count); @@ -63,7 +65,7 @@ impl Job for FileIdentifierJob { let file_paths = match block_on(get_orphan_file_paths(&ctx.core_ctx, cursor)) { Ok(file_paths) => file_paths, Err(e) => { - info!("Error getting orphan file paths: {}", e); + info!("Error getting orphan file paths: {:#?}", e); continue; } }; @@ -77,7 +79,7 @@ impl Job for FileIdentifierJob { // analyze each file_path for file_path in file_paths.iter() { // get the cas_id and extract metadata - match prepare_file(&location_path, file_path) { + match block_on(prepare_file(&location_path, file_path)) { Ok(file) => { let cas_id = file.cas_id.clone(); // create entry into chunks for created file data @@ -85,7 +87,7 @@ impl Job for FileIdentifierJob { cas_lookup.insert(cas_id, file_path.id); } Err(e) => { - info!("Error processing file: {}", e); + info!("Error processing file: {:#?}", e); continue; } }; @@ -106,8 +108,8 @@ impl Job for FileIdentifierJob { let file_path_id = cas_lookup.get(&file.cas_id).unwrap(); block_on( db.file_path() - .find_unique(file_path::id::equals(file_path_id.clone())) - .update(vec![file_path::file_id::set(Some(file.id.clone()))]) + .find_unique(file_path::id::equals(*file_path_id)) + .update(vec![file_path::file_id::set(Some(file.id))]) .exec(), ) .unwrap(); @@ -125,8 +127,8 @@ impl Job for FileIdentifierJob { for file in new_files.iter() { values.extend([ PrismaValue::String(file.cas_id.clone()), - PrismaValue::Int(file.size_in_bytes.clone()), - PrismaValue::DateTime(file.date_created.clone()), + PrismaValue::Int(file.size_in_bytes), + PrismaValue::DateTime(file.date_created), ]); } @@ -140,7 +142,7 @@ impl Job for FileIdentifierJob { values, ))) .unwrap_or_else(|e| { - info!("Error inserting files: {}", e); + info!("Error inserting files: {:#?}", e); Vec::new() }); @@ -151,8 +153,8 @@ impl Job for FileIdentifierJob { let file_path_id = cas_lookup.get(&file.cas_id).unwrap(); block_on( db.file_path() - .find_unique(file_path::id::equals(file_path_id.clone())) - .update(vec![file_path::file_id::set(Some(file.id.clone()))]) + .find_unique(file_path::id::equals(*file_path_id)) + .update(vec![file_path::file_id::set(Some(file.id))]) .exec(), ) .unwrap(); @@ -241,13 +243,15 @@ pub struct FileCreated { pub cas_id: String, } -pub fn prepare_file( - location_path: &str, +pub async fn prepare_file( + location_path: impl AsRef, file_path: &file_path::Data, ) -> Result { - let path = Path::new(&location_path).join(Path::new(file_path.materialized_path.as_str())); + let path = location_path + .as_ref() + .join(file_path.materialized_path.as_str()); - let metadata = fs::metadata(&path)?; + let metadata = fs::metadata(&path).await?; // let date_created: DateTime = metadata.created().unwrap().into(); @@ -255,7 +259,7 @@ pub fn prepare_file( let cas_id = { if !file_path.is_dir { - let mut ret = generate_cas_id(path.clone(), size.clone()).unwrap(); + let mut ret = generate_cas_id(path, size).await?; ret.truncate(16); ret } else { diff --git a/core/src/file/explorer/open.rs b/core/src/file/explorer/open.rs index bedfba5a0..901fa3589 100644 --- a/core/src/file/explorer/open.rs +++ b/core/src/file/explorer/open.rs @@ -6,33 +6,37 @@ use crate::{ sys::get_location, CoreContext, }; +use log::info; use std::path::Path; pub async fn open_dir( ctx: &CoreContext, - location_id: &i32, - path: &str, + location_id: i32, + path: impl AsRef, ) -> Result { - let db = &ctx.database; let config = get_nodestate(); // get location - let location = get_location(ctx, location_id.clone()).await?; + let location = get_location(ctx, location_id).await?; - let directory = db + let path_str = path.as_ref().to_string_lossy().to_string(); + + let directory = ctx + .database .file_path() .find_first(vec![ file_path::location_id::equals(Some(location.id)), - file_path::materialized_path::equals(path.into()), + file_path::materialized_path::equals(path_str), file_path::is_dir::equals(true), ]) .exec() .await? - .ok_or(FileError::DirectoryNotFound(path.to_string()))?; + .ok_or_else(|| FileError::DirectoryNotFound(path.as_ref().to_path_buf()))?; - println!("DIRECTORY: {:?}", directory); + info!("DIRECTORY: {:?}", directory); - let mut file_paths: Vec = db + let mut file_paths: Vec = ctx + .database .file_path() .find_many(vec![ file_path::location_id::equals(Some(location.id)), @@ -45,15 +49,17 @@ pub async fn open_dir( .map(Into::into) .collect(); - for file_path in &mut file_paths { - if let Some(file) = &mut file_path.file { - let thumb_path = Path::new(&config.data_path) - .join(THUMBNAIL_CACHE_DIR_NAME) - .join(format!("{}", location.id)) - .join(file.cas_id.clone()) - .with_extension("webp"); + if let Some(ref data_path) = config.data_path { + for file_path in &mut file_paths { + if let Some(file) = &mut file_path.file { + let thumb_path = data_path + .join(THUMBNAIL_CACHE_DIR_NAME) + .join(location.id.to_string()) + .join(file.cas_id.clone()) + .with_extension("webp"); - file.has_thumbnail = thumb_path.exists(); + file.has_thumbnail = thumb_path.exists(); + } } } diff --git a/core/src/file/indexer/mod.rs b/core/src/file/indexer/mod.rs index 4415b252c..d4e428a4d 100644 --- a/core/src/file/indexer/mod.rs +++ b/core/src/file/indexer/mod.rs @@ -1,15 +1,18 @@ use crate::job::{Job, JobReportUpdate, WorkerContext}; +use std::error::Error; +use std::path::PathBuf; use self::scan::ScanProgress; mod scan; +// Re-exporting pub use scan::*; -pub use scan::scan_path; +use scan::scan_path; #[derive(Debug)] pub struct IndexerJob { - pub path: String, + pub path: PathBuf, } #[async_trait::async_trait] @@ -17,9 +20,8 @@ impl Job for IndexerJob { fn name(&self) -> &'static str { "indexer" } - async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { - let core_ctx = ctx.core_ctx.clone(); - scan_path(&core_ctx, self.path.as_str(), move |p| { + async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { + scan_path(&ctx.core_ctx.clone(), &self.path, move |p| { ctx.progress( p.iter() .map(|p| match p.clone() { diff --git a/core/src/file/indexer/scan.rs b/core/src/file/indexer/scan.rs index 19c3ee53d..6dd7a5bb7 100644 --- a/core/src/file/indexer/scan.rs +++ b/core/src/file/indexer/scan.rs @@ -1,13 +1,22 @@ -use crate::sys::{create_location, LocationResource}; -use crate::CoreContext; -use chrono::{DateTime, FixedOffset, Utc}; +use crate::{ + sys::{create_location, LocationResource}, + CoreContext, +}; + +use chrono::{DateTime, Utc}; use log::{error, info}; use prisma_client_rust::prisma_models::PrismaValue; use prisma_client_rust::raw; use prisma_client_rust::raw::Raw; use serde::{Deserialize, Serialize}; use std::ffi::OsStr; -use std::{collections::HashMap, fs, path::Path, path::PathBuf, time::Instant}; +use std::fmt::Debug; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + time::Instant, +}; +use tokio::fs; use walkdir::{DirEntry, WalkDir}; #[derive(Clone)] @@ -22,13 +31,10 @@ static BATCH_SIZE: usize = 100; // creates a vector of valid path buffers from a directory pub async fn scan_path( ctx: &CoreContext, - path: &str, + path: impl AsRef + Debug, on_progress: impl Fn(Vec) + Send + Sync + 'static, ) -> Result<(), Box> { - let db = &ctx.database; - let path = path.to_string(); - - let location = create_location(&ctx, &path).await?; + let location = create_location(ctx, &path).await?; // query db to highers id, so we can increment it for the new files indexed #[derive(Deserialize, Serialize, Debug)] @@ -36,20 +42,22 @@ pub async fn scan_path( id: Option, } // grab the next id so we can increment in memory for batch inserting - let first_file_id = match db + let first_file_id = match ctx + .database ._query_raw::(raw!("SELECT MAX(id) id FROM file_paths")) .await { Ok(rows) => rows[0].id.unwrap_or(0), - Err(e) => panic!("Error querying for next file id: {}", e), + Err(e) => panic!("Error querying for next file id: {:#?}", e), }; //check is path is a directory - if !PathBuf::from(&path).is_dir() { + if !path.as_ref().is_dir() { // return Err(anyhow::anyhow!("{} is not a directory", &path)); - panic!("{} is not a directory", &path); + panic!("{:#?} is not a directory", path); } - let dir_path = path.clone(); + + let path_buf = path.as_ref().to_path_buf(); // spawn a dedicated thread to scan the directory for performance let (paths, scan_start, on_progress) = tokio::task::spawn_blocking(move || { @@ -66,10 +74,9 @@ pub async fn scan_path( next_file_id }; // walk through directory recursively - for entry in WalkDir::new(&dir_path).into_iter().filter_entry(|dir| { - let approved = - !is_hidden(dir) && !is_app_bundle(dir) && !is_node_modules(dir) && !is_library(dir); - approved + for entry in WalkDir::new(path_buf).into_iter().filter_entry(|dir| { + // check if entry is approved + !is_hidden(dir) && !is_app_bundle(dir) && !is_node_modules(dir) && !is_library(dir) }) { // extract directory entry or log and continue if failed let entry = match entry { @@ -85,7 +92,7 @@ pub async fn scan_path( let parent_path = path .parent() - .unwrap_or(Path::new("")) + .unwrap_or_else(|| Path::new("")) .to_str() .unwrap_or(""); let parent_dir_id = dirs.get(&*parent_path); @@ -99,7 +106,7 @@ pub async fn scan_path( }; on_progress(vec![ - ScanProgress::Message(format!("{}", path_str)), + ScanProgress::Message(format!("Scanning {}", path_str)), ScanProgress::ChunkCount(paths.len() / BATCH_SIZE), ]); @@ -121,8 +128,7 @@ pub async fn scan_path( } (paths, scan_start, on_progress) }) - .await - .unwrap(); + .await?; let db_write_start = Instant::now(); let scan_read_time = scan_start.elapsed(); @@ -142,7 +148,7 @@ pub async fn scan_path( for (file_path, file_id, parent_dir_id, is_dir) in chunk { files.extend( - match prepare_values(&file_path, *file_id, &location, parent_dir_id, *is_dir) { + match prepare_values(file_path, *file_id, &location, parent_dir_id, *is_dir).await { Ok(values) => values.to_vec(), Err(e) => { error!("Error creating file model from path {:?}: {}", file_path, e); @@ -162,7 +168,7 @@ pub async fn scan_path( files ); - let count = db._execute_raw(raw).await; + let count = ctx.database._execute_raw(raw).await; info!("Inserted {:?} records", count); } @@ -177,14 +183,14 @@ pub async fn scan_path( } // reads a file at a path and creates an ActiveModel with metadata -fn prepare_values( +async fn prepare_values( file_path: &PathBuf, id: i32, location: &LocationResource, parent_id: &Option, is_dir: bool, ) -> Result<[PrismaValue; 8], std::io::Error> { - let metadata = fs::metadata(&file_path)?; + let metadata = fs::metadata(&file_path).await?; let location_path = Path::new(location.path.as_ref().unwrap().as_str()); // let size = metadata.len(); let name; @@ -214,7 +220,6 @@ fn prepare_values( PrismaValue::String(name), PrismaValue::String(extension.to_lowercase()), parent_id - .clone() .map(|id| PrismaValue::Int(id as i64)) .unwrap_or(PrismaValue::Null), PrismaValue::DateTime(date_created.into()), @@ -236,7 +241,7 @@ fn is_hidden(entry: &DirEntry) -> bool { entry .file_name() .to_str() - .map(|s| s.starts_with(".")) + .map(|s| s.starts_with('.')) .unwrap_or(false) } @@ -265,7 +270,7 @@ fn is_app_bundle(entry: &DirEntry) -> bool { .map(|s| s.contains(".app") | s.contains(".bundle")) .unwrap_or(false); - let is_app_bundle = is_dir && contains_dot; + // let is_app_bundle = is_dir && contains_dot; // if is_app_bundle { // let path_buff = entry.path(); // let path = path_buff.to_str().unwrap(); @@ -273,5 +278,5 @@ fn is_app_bundle(entry: &DirEntry) -> bool { // self::path(&path, ); // } - is_app_bundle + is_dir && contains_dot } diff --git a/core/src/file/mod.rs b/core/src/file/mod.rs index bc632ecec..9959af3ed 100644 --- a/core/src/file/mod.rs +++ b/core/src/file/mod.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use int_enum::IntEnum; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -77,46 +78,52 @@ pub enum FileKind { Alias = 8, } -impl Into for file::Data { - fn into(self) -> File { - File { - id: self.id, - cas_id: self.cas_id, - integrity_checksum: self.integrity_checksum, - kind: IntEnum::from_int(self.kind).unwrap(), - size_in_bytes: self.size_in_bytes.to_string(), - // encryption: EncryptionAlgorithm::from_int(self.encryption).unwrap(), - ipfs_id: self.ipfs_id, - hidden: self.hidden, - favorite: self.favorite, - important: self.important, - has_thumbnail: self.has_thumbnail, - has_thumbstrip: self.has_thumbstrip, - has_video_preview: self.has_video_preview, - note: self.note, - date_created: self.date_created.into(), - date_modified: self.date_modified.into(), - date_indexed: self.date_indexed.into(), +impl From for File { + fn from(data: file::Data) -> Self { + Self { + id: data.id, + cas_id: data.cas_id, + integrity_checksum: data.integrity_checksum, + kind: IntEnum::from_int(data.kind).unwrap(), + size_in_bytes: data.size_in_bytes.to_string(), + // encryption: EncryptionAlgorithm::from_int(data.encryption).unwrap(), + ipfs_id: data.ipfs_id, + hidden: data.hidden, + favorite: data.favorite, + important: data.important, + has_thumbnail: data.has_thumbnail, + has_thumbstrip: data.has_thumbstrip, + has_video_preview: data.has_video_preview, + note: data.note, + date_created: data.date_created.into(), + date_modified: data.date_modified.into(), + date_indexed: data.date_indexed.into(), paths: vec![], } } } -impl Into for file_path::Data { - fn into(mut self) -> FilePath { - FilePath { - id: self.id, - is_dir: self.is_dir, - materialized_path: self.materialized_path, - file_id: self.file_id, - parent_id: self.parent_id, - location_id: self.location_id.unwrap_or(0), - date_indexed: self.date_indexed.into(), - name: self.name, - extension: self.extension, - date_created: self.date_created.into(), - date_modified: self.date_modified.into(), - file: self.file.take().unwrap_or(None).map(|file| (*file).into()), +impl From> for File { + fn from(data: Box) -> Self { + Self::from(*data) + } +} + +impl From for FilePath { + fn from(data: file_path::Data) -> Self { + Self { + id: data.id, + is_dir: data.is_dir, + materialized_path: data.materialized_path, + file_id: data.file_id, + parent_id: data.parent_id, + location_id: data.location_id.unwrap_or(0), + date_indexed: data.date_indexed.into(), + name: data.name, + extension: data.extension, + date_created: data.date_created.into(), + date_modified: data.date_modified.into(), + file: data.file.unwrap_or(None).map(Into::into), } } } @@ -131,9 +138,9 @@ pub struct DirectoryWithContents { #[derive(Error, Debug)] pub enum FileError { #[error("Directory not found (path: {0:?})")] - DirectoryNotFound(String), + DirectoryNotFound(PathBuf), #[error("File not found (path: {0:?})")] - FileNotFound(String), + FileNotFound(PathBuf), #[error("Database error")] DatabaseError(#[from] prisma::QueryError), #[error("System error")] @@ -145,7 +152,7 @@ pub async fn set_note( id: i32, note: Option, ) -> Result { - let response = ctx + let _response = ctx .database .file() .find_unique(file::id::equals(id)) diff --git a/core/src/job/jobs.rs b/core/src/job/jobs.rs index efacd6cb8..3a0b56c5a 100644 --- a/core/src/job/jobs.rs +++ b/core/src/job/jobs.rs @@ -23,8 +23,8 @@ const MAX_WORKERS: usize = 1; #[async_trait::async_trait] pub trait Job: Send + Sync + Debug { - async fn run(&self, ctx: WorkerContext) -> Result<(), Box>; fn name(&self) -> &'static str; + async fn run(&self, ctx: WorkerContext) -> Result<(), Box>; } // jobs struct is maintained by the core @@ -52,7 +52,7 @@ impl Jobs { let wrapped_worker = Arc::new(Mutex::new(worker)); - Worker::spawn(wrapped_worker.clone(), ctx).await; + Worker::spawn(Arc::clone(&wrapped_worker), ctx).await; self.running_workers.insert(id, wrapped_worker); } else { @@ -84,9 +84,8 @@ impl Jobs { } pub async fn queue_pending_job(ctx: &CoreContext) -> Result<(), JobError> { - let db = &ctx.database; - - let next_job = db + let _next_job = ctx + .database .job() .find_first(vec![job::status::equals(JobStatus::Queued.int_value())]) .exec() @@ -96,14 +95,14 @@ impl Jobs { } pub async fn get_history(ctx: &CoreContext) -> Result, JobError> { - let db = &ctx.database; - let jobs = db + let jobs = ctx + .database .job() .find_many(vec![job::status::not(JobStatus::Running.int_value())]) .exec() .await?; - Ok(jobs.into_iter().map(|j| j.into()).collect()) + Ok(jobs.into_iter().map(Into::into).collect()) } } @@ -138,20 +137,20 @@ pub struct JobReport { } // convert database struct into a resource struct -impl Into for job::Data { - fn into(self) -> JobReport { +impl From for JobReport { + fn from(data: job::Data) -> JobReport { JobReport { - id: self.id, - name: self.name, - // client_id: self.client_id, - status: JobStatus::from_int(self.status).unwrap(), - task_count: self.task_count, - completed_task_count: self.completed_task_count, - date_created: self.date_created.into(), - date_modified: self.date_modified.into(), - data: self.data, + id: data.id, + name: data.name, + // client_id: data.client_id, + status: JobStatus::from_int(data.status).unwrap(), + task_count: data.task_count, + completed_task_count: data.completed_task_count, + date_created: data.date_created.into(), + date_modified: data.date_modified.into(), + data: data.data, message: String::new(), - seconds_elapsed: self.seconds_elapsed, + seconds_elapsed: data.seconds_elapsed, } } } @@ -177,7 +176,7 @@ impl JobReport { let mut params = Vec::new(); - if let Some(_) = &self.data { + if self.data.is_some() { params.push(job::data::set(self.data.clone())) } diff --git a/core/src/job/worker.rs b/core/src/job/worker.rs index 6022e603e..1acf4f250 100644 --- a/core/src/job/worker.rs +++ b/core/src/job/worker.rs @@ -3,6 +3,7 @@ use super::{ Job, }; use crate::{ClientQuery, CoreContext, CoreEvent, InternalEvent}; +use log::error; use std::{sync::Arc, time::Duration}; use tokio::{ sync::{ @@ -11,6 +12,8 @@ use tokio::{ }, time::{sleep, Instant}, }; +use uuid::Uuid; + // used to update the worker state from inside the worker thread pub enum WorkerEvent { Progressed(Vec), @@ -53,7 +56,7 @@ pub struct Worker { impl Worker { pub fn new(job: Box) -> Self { let (worker_sender, worker_receiver) = unbounded_channel(); - let uuid = uuid::Uuid::new_v4().to_string(); + let uuid = Uuid::new_v4().to_string(); let name = job.name(); Self { @@ -80,7 +83,7 @@ impl Worker { worker_mut.job_report.status = JobStatus::Running; - worker_mut.job_report.create(&ctx).await.unwrap_or(()); + worker_mut.job_report.create(ctx).await.unwrap_or(()); // spawn task to handle receiving events from the worker tokio::spawn(Worker::track_progress( @@ -116,7 +119,7 @@ impl Worker { let result = job.run(worker_ctx.clone()).await; if let Err(e) = result { - println!("job failed {:?}", e); + error!("job failed {:?}", e); worker_ctx.sender.send(WorkerEvent::Failed).unwrap_or(()); } else { // handle completion diff --git a/core/src/lib.rs b/core/src/lib.rs index bb096a666..f0b0833d2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -3,13 +3,18 @@ use crate::{ prisma::file as prisma_file, prisma::location, util::db::create_connection, }; use job::{Job, JobReport, Jobs}; +use log::{error, info}; use prisma::PrismaClient; use serde::{Deserialize, Serialize}; -use std::{fs, sync::Arc}; +use std::path::PathBuf; +use std::sync::Arc; use thiserror::Error; -use tokio::sync::{ - mpsc::{self, unbounded_channel, UnboundedReceiver, UnboundedSender}, - oneshot, +use tokio::{ + fs, + sync::{ + mpsc::{self, unbounded_channel, UnboundedReceiver, UnboundedSender}, + oneshot, + }, }; use ts_rs::TS; @@ -83,19 +88,19 @@ impl CoreContext { self.internal_sender .send(InternalEvent::JobIngest(job)) .unwrap_or_else(|e| { - println!("Failed to spawn job. {:?}", e); + error!("Failed to spawn job. {:?}", e); }); } pub fn queue_job(&self, job: Box) { self.internal_sender .send(InternalEvent::JobQueue(job)) .unwrap_or_else(|e| { - println!("Failed to queue job. {:?}", e); + error!("Failed to queue job. {:?}", e); }); } pub async fn emit(&self, event: CoreEvent) { self.event_sender.send(event).await.unwrap_or_else(|e| { - println!("Failed to emit event. {:?}", e); + error!("Failed to emit event. {:?}", e); }); } } @@ -127,23 +132,23 @@ pub struct Node { impl Node { // create new instance of node, run startup tasks - pub async fn new(mut data_dir: std::path::PathBuf) -> (Node, mpsc::Receiver) { + pub async fn new(mut data_dir: PathBuf) -> (Node, mpsc::Receiver) { let (event_sender, event_recv) = mpsc::channel(100); - data_dir = data_dir.join("spacedrive"); - let data_dir = data_dir.to_str().unwrap(); + data_dir.push("spacedrive"); // create data directory if it doesn't exist - fs::create_dir_all(&data_dir).unwrap(); + fs::create_dir_all(&data_dir).await.unwrap(); // prepare basic client state - let mut state = NodeState::new(data_dir, "diamond-mastering-space-dragon").unwrap(); + let mut state = NodeState::new(data_dir.clone(), "diamond-mastering-space-dragon").unwrap(); // load from disk state .read_disk() - .unwrap_or(println!("Error: No node state found, creating new one...")); + .await + .unwrap_or_else(|_| error!("Error: No node state found, creating new one...")); - state.save(); + state.save().await; - println!("Node State: {:?}", state); + info!("Node State: {:?}", state); // connect to default library let database = Arc::new( @@ -213,32 +218,32 @@ impl Node { } // load library database + initialize client with db pub async fn initializer(&self) { - println!("Initializing..."); + info!("Initializing..."); let ctx = self.get_context(); - if self.state.libraries.len() == 0 { + if self.state.libraries.is_empty() { match library::create(&ctx, None).await { - Ok(library) => println!("Created new library: {:?}", library), - Err(e) => println!("Error creating library: {:?}", e), + Ok(library) => info!("Created new library: {:?}", library), + Err(e) => error!("Error creating library: {:?}", e), } } else { for library in self.state.libraries.iter() { // init database for library match library::load(&ctx, &library.library_path, &library.library_uuid).await { - Ok(library) => println!("Loaded library: {:?}", library), - Err(e) => println!("Error loading library: {:?}", e), + Ok(library) => info!("Loaded library: {:?}", library), + Err(e) => error!("Error loading library: {:?}", e), } } } // init node data within library - match node::LibraryNode::create(&self).await { - Ok(_) => println!("Spacedrive online"), - Err(e) => println!("Error initializing node: {:?}", e), + match node::LibraryNode::create(self).await { + Ok(_) => info!("Spacedrive online"), + Err(e) => error!("Error initializing node: {:?}", e), }; } async fn exec_command(&mut self, cmd: ClientCommand) -> Result { - println!("Core command: {:?}", cmd); + info!("Core command: {:?}", cmd); let ctx = self.get_context(); Ok(match cmd { // CRUD for locations @@ -299,7 +304,7 @@ impl Node { CoreResponse::Success(()) } // ClientCommand::PurgeDatabase => { - // println!("Purging database..."); + // info!("Purging database..."); // fs::remove_file(Path::new(&self.state.data_path).join("library.db")).unwrap(); // CoreResponse::Success(()) // } @@ -334,7 +339,7 @@ impl Node { location_id, limit: _, } => CoreResponse::LibGetExplorerDir( - file::explorer::open_dir(&ctx, &location_id, &path).await?, + file::explorer::open_dir(&ctx, location_id, &path).await?, ), ClientQuery::LibGetTags => todo!(), ClientQuery::JobGetRunning => { @@ -369,15 +374,15 @@ pub enum ClientCommand { TagAssign { file_id: i32, tag_id: i32 }, TagDelete { id: i32 }, // Locations - LocCreate { path: String }, + LocCreate { path: PathBuf }, LocUpdate { id: i32, name: Option }, LocDelete { id: i32 }, LocRescan { id: i32 }, // System SysVolumeUnmount { id: i32 }, - GenerateThumbsForLocation { id: i32, path: String }, + GenerateThumbsForLocation { id: i32, path: PathBuf }, // PurgeDatabase, - IdentifyUniqueFiles { id: i32, path: String }, + IdentifyUniqueFiles { id: i32, path: PathBuf }, } // represents an event this library can emit diff --git a/core/src/library/loader.rs b/core/src/library/loader.rs index c2fa47beb..da4826f7b 100644 --- a/core/src/library/loader.rs +++ b/core/src/library/loader.rs @@ -1,16 +1,20 @@ +use log::info; +use std::fmt::Debug; +use std::path::{Path, PathBuf}; use uuid::Uuid; -use crate::node::{get_nodestate, LibraryState}; -use crate::prisma::library; -use crate::util::db::{run_migrations, DatabaseError}; -use crate::CoreContext; +use crate::{ + node::{get_nodestate, LibraryState}, + prisma::library, + util::db::{run_migrations, DatabaseError}, + CoreContext, +}; pub static LIBRARY_DB_NAME: &str = "library.db"; pub static DEFAULT_NAME: &str = "My Library"; -pub fn get_library_path(data_path: &str) -> String { - let path = data_path.to_owned(); - format!("{}/{}", path, LIBRARY_DB_NAME) +pub fn get_library_path(data_path: impl AsRef) -> PathBuf { + data_path.as_ref().join(LIBRARY_DB_NAME) } // pub async fn get(core: &Node) -> Result { @@ -19,7 +23,7 @@ pub fn get_library_path(data_path: &str) -> String { // let library_state = config.get_current_library(); -// println!("{:?}", library_state); +// info!("{:?}", library_state); // // get library from db // let library = match db @@ -42,19 +46,19 @@ pub fn get_library_path(data_path: &str) -> String { pub async fn load( ctx: &CoreContext, - library_path: &str, + library_path: impl AsRef + Debug, library_id: &str, ) -> Result<(), DatabaseError> { let mut config = get_nodestate(); - println!("Initializing library: {} {}", &library_id, library_path); + info!("Initializing library: {} {:#?}", &library_id, library_path); if config.current_library_uuid != library_id { config.current_library_uuid = library_id.to_string(); - config.save(); + config.save().await; } // create connection with library database & run migrations - run_migrations(&ctx).await?; + run_migrations(ctx).await?; // if doesn't exist, mark as offline Ok(()) } @@ -64,36 +68,35 @@ pub async fn create(ctx: &CoreContext, name: Option) -> Result<(), ()> { let uuid = Uuid::new_v4().to_string(); - println!("Creating library {:?}, UUID: {:?}", name, uuid); + info!("Creating library {:?}, UUID: {:?}", name, uuid); let library_state = LibraryState { library_uuid: uuid.clone(), - library_path: get_library_path(&config.data_path), + library_path: get_library_path(config.data_path.as_ref().unwrap()), ..LibraryState::default() }; - run_migrations(&ctx).await.unwrap(); + run_migrations(ctx).await.unwrap(); config.libraries.push(library_state); config.current_library_uuid = uuid; - config.save(); + config.save().await; - let db = &ctx.database; - - let library = db + let library = ctx + .database .library() .create( library::pub_id::set(config.current_library_uuid), - library::name::set(name.unwrap_or(DEFAULT_NAME.into())), + library::name::set(name.unwrap_or_else(|| DEFAULT_NAME.into())), vec![], ) .exec() .await .unwrap(); - println!("library created in database: {:?}", library); + info!("library created in database: {:?}", library); Ok(()) } diff --git a/core/src/library/statistics.rs b/core/src/library/statistics.rs index f866999b5..91157286c 100644 --- a/core/src/library/statistics.rs +++ b/core/src/library/statistics.rs @@ -5,13 +5,14 @@ use crate::{ CoreContext, }; use fs_extra::dir::get_size; +use log::info; use serde::{Deserialize, Serialize}; -use std::fs; +use tokio::fs; use ts_rs::TS; use super::LibraryError; -#[derive(Debug, Serialize, Deserialize, TS, Clone)] +#[derive(Debug, Serialize, Deserialize, TS, Clone, Default)] #[ts(export)] pub struct Statistics { pub total_file_count: i32, @@ -23,29 +24,15 @@ pub struct Statistics { pub library_db_size: String, } -impl Into for Data { - fn into(self) -> Statistics { - Statistics { - total_file_count: self.total_file_count, - total_bytes_used: self.total_bytes_used, - total_bytes_capacity: self.total_bytes_capacity, - total_bytes_free: self.total_bytes_free, - total_unique_bytes: self.total_unique_bytes, - preview_media_bytes: self.preview_media_bytes, - library_db_size: String::new(), - } - } -} - -impl Default for Statistics { - fn default() -> Self { +impl From for Statistics { + fn from(data: Data) -> Self { Self { - total_file_count: 0, - total_bytes_used: String::new(), - total_bytes_capacity: String::new(), - total_bytes_free: String::new(), - total_unique_bytes: String::new(), - preview_media_bytes: String::new(), + total_file_count: data.total_file_count, + total_bytes_used: data.total_bytes_used, + total_bytes_capacity: data.total_bytes_capacity, + total_bytes_free: data.total_bytes_free, + total_unique_bytes: data.total_unique_bytes, + preview_media_bytes: data.preview_media_bytes, library_db_size: String::new(), } } @@ -54,33 +41,29 @@ impl Default for Statistics { impl Statistics { pub async fn retrieve(ctx: &CoreContext) -> Result { let config = get_nodestate(); - let db = &ctx.database; let library_data = config.get_current_library(); - let library_statistics_db = match db + let library_statistics_db = ctx + .database .library_statistics() .find_unique(id::equals(library_data.library_id)) .exec() .await? - { - Some(library_statistics_db) => library_statistics_db.into(), - // create the default values if database has no entry - None => Statistics::default(), - }; - Ok(library_statistics_db.into()) + .map_or_else(Default::default, Into::into); + Ok(library_statistics_db) } pub async fn calculate(ctx: &CoreContext) -> Result { let config = get_nodestate(); - let db = &ctx.database; // get library from client state let library_data = config.get_current_library(); - println!( + info!( "Calculating library statistics {:?}", library_data.library_uuid ); // get library from db - let library = db + let library = ctx + .database .library() .find_unique(library::pub_id::equals( library_data.library_uuid.to_string(), @@ -92,7 +75,8 @@ impl Statistics { return Err(LibraryError::LibraryNotFound); } - let library_statistics = db + let library_statistics = ctx + .database .library_statistics() .find_unique(id::equals(library_data.library_id)) .exec() @@ -100,9 +84,9 @@ impl Statistics { // TODO: get from database, not sys let volumes = Volume::get_volumes(); - Volume::save(&ctx).await?; + Volume::save(ctx).await?; - // println!("{:?}", volumes); + // info!("{:?}", volumes); let mut available_capacity: u64 = 0; let mut total_capacity: u64 = 0; @@ -113,14 +97,14 @@ impl Statistics { } } - let library_db_size = match fs::metadata(library_data.library_path.as_str()) { + let library_db_size = match fs::metadata(library_data.library_path).await { Ok(metadata) => metadata.len(), Err(_) => 0, }; - println!("{:?}", library_statistics); + info!("{:?}", library_statistics); - let thumbnail_folder_size = get_size(&format!("{}/{}", config.data_path, "thumbnails")); + let thumbnail_folder_size = get_size(config.data_path.unwrap().join("thumbnails")); let statistics = Statistics { library_db_size: library_db_size.to_string(), @@ -135,7 +119,8 @@ impl Statistics { None => library_data.library_id, }; - db.library_statistics() + ctx.database + .library_statistics() .upsert( library_id::equals(library_local_id), ( @@ -143,7 +128,7 @@ impl Statistics { vec![library_db_size::set(statistics.library_db_size.clone())], ), vec![ - total_file_count::set(statistics.total_file_count.clone()), + total_file_count::set(statistics.total_file_count), total_bytes_used::set(statistics.total_bytes_used.clone()), total_bytes_capacity::set(statistics.total_bytes_capacity.clone()), total_bytes_free::set(statistics.total_bytes_free.clone()), diff --git a/core/src/node/mod.rs b/core/src/node/mod.rs index 52bed0ef9..309815c97 100644 --- a/core/src/node/mod.rs +++ b/core/src/node/mod.rs @@ -4,6 +4,7 @@ use crate::{ }; use chrono::{DateTime, Utc}; use int_enum::IntEnum; +use log::info; use serde::{Deserialize, Serialize}; use std::env; use thiserror::Error; @@ -22,17 +23,24 @@ pub struct LibraryNode { pub last_seen: DateTime, } -impl Into for node::Data { - fn into(self) -> LibraryNode { - LibraryNode { - uuid: self.pub_id, - name: self.name, - platform: IntEnum::from_int(self.platform).unwrap(), - last_seen: self.last_seen.into(), +impl From for LibraryNode { + fn from(data: node::Data) -> Self { + Self { + uuid: data.pub_id, + name: data.name, + platform: IntEnum::from_int(data.platform).unwrap(), + last_seen: data.last_seen.into(), } } } +impl From> for LibraryNode { + fn from(data: Box) -> Self { + Self::from(*data) + } +} + +#[allow(clippy::upper_case_acronyms)] #[repr(i32)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, TS, Eq, PartialEq, IntEnum)] #[ts(export)] @@ -47,11 +55,9 @@ pub enum Platform { impl LibraryNode { pub async fn create(node: &Node) -> Result<(), NodeError> { - println!("Creating node..."); + info!("Creating node..."); let mut config = state::get_nodestate(); - let db = &node.database; - let hostname = match hostname::get() { Ok(hostname) => hostname.to_str().unwrap_or_default().to_owned(), Err(_) => "unknown".to_owned(), @@ -64,30 +70,31 @@ impl LibraryNode { _ => Platform::Unknown, }; - let _node = match db + let node = if let Some(node) = node + .database .node() .find_unique(node::pub_id::equals(config.node_pub_id.clone())) .exec() .await? { - Some(node) => node, - None => { - db.node() - .create( - node::pub_id::set(config.node_pub_id.clone()), - node::name::set(hostname.clone()), - vec![node::platform::set(platform as i32)], - ) - .exec() - .await? - } + node + } else { + node.database + .node() + .create( + node::pub_id::set(config.node_pub_id.clone()), + node::name::set(hostname.clone()), + vec![node::platform::set(platform as i32)], + ) + .exec() + .await? }; config.node_name = hostname; - config.node_id = _node.id; - config.save(); + config.node_id = node.id; + config.save().await; - println!("node: {:?}", &_node); + info!("node: {:?}", node); Ok(()) } diff --git a/core/src/node/state.rs b/core/src/node/state.rs index b7124686f..6892b0d37 100644 --- a/core/src/node/state.rs +++ b/core/src/node/state.rs @@ -1,8 +1,12 @@ use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; -use std::fs; -use std::io::{BufReader, Write}; +use std::path::PathBuf; use std::sync::RwLock; +use tokio::io::AsyncReadExt; +use tokio::{ + fs, + io::{AsyncWriteExt, BufReader}, +}; use ts_rs::TS; use uuid::Uuid; @@ -13,7 +17,7 @@ pub struct NodeState { pub node_id: i32, pub node_name: String, // config path is stored as struct can exist only in memory during startup and be written to disk later without supplying path - pub data_path: String, + pub data_path: Option, // the port this node uses to listen for incoming connections pub tcp_port: u32, // all the libraries loaded by this node @@ -29,7 +33,7 @@ pub static NODE_STATE_CONFIG_NAME: &str = "node_state.json"; pub struct LibraryState { pub library_uuid: String, pub library_id: i32, - pub library_path: String, + pub library_path: PathBuf, pub offline: bool, } @@ -39,49 +43,50 @@ lazy_static! { } pub fn get_nodestate() -> NodeState { - match CONFIG.read() { - Ok(guard) => guard.clone().unwrap_or(NodeState::default()), - Err(_) => return NodeState::default(), + if let Ok(guard) = CONFIG.read() { + guard.clone().unwrap_or_default() + } else { + NodeState::default() } } impl NodeState { - pub fn new(data_path: &str, node_name: &str) -> Result { + pub fn new(data_path: PathBuf, node_name: &str) -> Result { let uuid = Uuid::new_v4().to_string(); // create struct and assign defaults let config = Self { node_pub_id: uuid, - data_path: data_path.to_string(), + data_path: Some(data_path), node_name: node_name.to_string(), ..Default::default() }; Ok(config) } - pub fn save(&self) { + pub async fn save(&self) { self.write_memory(); // only write to disk if config path is set - if !&self.data_path.is_empty() { - let config_path = format!("{}/{}", &self.data_path, NODE_STATE_CONFIG_NAME); - let mut file = fs::File::create(config_path).unwrap(); + if let Some(ref data_path) = self.data_path { + let config_path = data_path.join(NODE_STATE_CONFIG_NAME); + let mut file = fs::File::create(config_path).await.unwrap(); let json = serde_json::to_string(&self).unwrap(); - file.write_all(json.as_bytes()).unwrap(); + file.write_all(json.as_bytes()).await.unwrap(); } } - pub fn read_disk(&mut self) -> Result<(), ()> { - let config_path = format!("{}/{}", &self.data_path, NODE_STATE_CONFIG_NAME); - - // open the file and parse json - match fs::File::open(config_path) { - Ok(file) => { - let reader = BufReader::new(file); - let data = serde_json::from_reader(reader).unwrap(); + pub async fn read_disk(&mut self) -> Result<(), ()> { + if let Some(ref data_path) = self.data_path { + let config_path = data_path.join(NODE_STATE_CONFIG_NAME); + // open the file and parse json + if let Ok(file) = fs::File::open(config_path).await { + let mut buf = vec![]; + let bytes = BufReader::new(file).read_to_end(&mut buf).await.unwrap(); + let data = serde_json::from_slice(&buf[..bytes]).unwrap(); // assign to self *self = data; } - _ => {} } + Ok(()) } @@ -91,17 +96,14 @@ impl NodeState { } pub fn get_current_library(&self) -> LibraryState { - match self - .libraries + self.libraries .iter() .find(|lib| lib.library_uuid == self.current_library_uuid) - { - Some(lib) => lib.clone(), - None => LibraryState::default(), - } + .cloned() + .unwrap_or_default() } - pub fn get_current_library_db_path(&self) -> String { - format!("{}/library.db", &self.get_current_library().library_path) + pub fn get_current_library_db_path(&self) -> PathBuf { + self.get_current_library().library_path.join("library.db") } } diff --git a/core/src/sys/locations.rs b/core/src/sys/locations.rs index 0b4dafac3..162c80b91 100644 --- a/core/src/sys/locations.rs +++ b/core/src/sys/locations.rs @@ -1,15 +1,22 @@ use crate::{ - encode::ThumbnailJob, file::{cas::FileIdentifierJob, indexer::IndexerJob}, node::{get_nodestate, LibraryNode}, prisma::{file_path, location}, ClientQuery, CoreContext, CoreEvent, }; -use prisma_client_rust::{raw, PrismaValue}; + +use log::info; use serde::{Deserialize, Serialize}; -use std::{fs, io, io::Write, path::Path}; +use std::fmt::Debug; +use std::path::{Path, PathBuf}; use thiserror::Error; +use tokio::io::AsyncWriteExt; +use tokio::{ + fs::{metadata, File}, + io, +}; use ts_rs::TS; +use uuid::Uuid; use super::SysError; @@ -28,25 +35,25 @@ pub struct LocationResource { pub date_created: chrono::DateTime, } -impl Into for location::Data { - fn into(mut self) -> LocationResource { +impl From for LocationResource { + fn from(data: location::Data) -> Self { LocationResource { - id: self.id, - name: self.name, - path: self.local_path, - total_capacity: self.total_capacity, - available_capacity: self.available_capacity, - is_removable: self.is_removable, - node: self.node.take().unwrap_or(None).map(|node| (*node).into()), - is_online: self.is_online, - date_created: self.date_created.into(), + id: data.id, + name: data.name, + path: data.local_path, + total_capacity: data.total_capacity, + available_capacity: data.available_capacity, + is_removable: data.is_removable, + node: data.node.unwrap_or(None).map(Into::into), + is_online: data.is_online, + date_created: data.date_created.into(), } } } #[derive(Serialize, Deserialize, Default)] pub struct DotSpacedrive { - pub location_uuid: String, + pub location_uuid: Uuid, pub library_uuid: String, } @@ -69,24 +76,25 @@ pub async fn get_location( ctx: &CoreContext, location_id: i32, ) -> Result { - let db = &ctx.database; - // get location by location_id from db and include location_paths - let location = match db + ctx.database .location() .find_unique(location::id::equals(location_id)) .exec() .await? - { - Some(location) => location, - None => Err(LocationError::NotFound(location_id.to_string()))?, - }; - Ok(location.into()) + .map(Into::into) + .ok_or_else(|| LocationError::IdNotFound(location_id).into()) } -pub fn scan_location(ctx: &CoreContext, location_id: i32, path: String) { - ctx.spawn_job(Box::new(IndexerJob { path: path.clone() })); - ctx.queue_job(Box::new(FileIdentifierJob { location_id, path })); +pub fn scan_location(ctx: &CoreContext, location_id: i32, path: impl AsRef) { + let path_buf = path.as_ref().to_path_buf(); + ctx.spawn_job(Box::new(IndexerJob { + path: path_buf.clone(), + })); + ctx.queue_job(Box::new(FileIdentifierJob { + location_id, + path: path_buf, + })); // TODO: make a way to stop jobs so this can be canceled without rebooting app // ctx.queue_job(Box::new(ThumbnailJob { // location_id, @@ -97,19 +105,18 @@ pub fn scan_location(ctx: &CoreContext, location_id: i32, path: String) { pub async fn new_location_and_scan( ctx: &CoreContext, - path: &str, + path: impl AsRef + Debug, ) -> Result { - let location = create_location(&ctx, path).await?; + let location = create_location(ctx, &path).await?; - scan_location(&ctx, location.id, path.to_string()); + scan_location(ctx, location.id, path); Ok(location) } pub async fn get_locations(ctx: &CoreContext) -> Result, SysError> { - let db = &ctx.database; - - let locations = db + let locations = ctx + .database .location() .find_many(vec![]) .with(location::node::fetch()) @@ -117,119 +124,107 @@ pub async fn get_locations(ctx: &CoreContext) -> Result, S .await?; // turn locations into LocationResource - let locations: Vec = locations - .into_iter() - .map(|location| location.into()) - .collect(); - - Ok(locations) + Ok(locations.into_iter().map(LocationResource::from).collect()) } -pub async fn create_location(ctx: &CoreContext, path: &str) -> Result { - let db = &ctx.database; - let config = get_nodestate(); +pub async fn create_location( + ctx: &CoreContext, + path: impl AsRef + Debug, +) -> Result { + let path = path.as_ref(); // check if we have access to this location - if !Path::new(path).exists() { - Err(LocationError::NotFound(path.to_string()))?; + if !path.exists() { + return Err(LocationError::PathNotFound(path.to_owned()).into()); } - // if on windows - if cfg!(target_family = "windows") { - // try and create a dummy file to see if we can write to this location - match fs::File::create(format!("{}/{}", path.clone(), ".spacewrite")) { - Ok(file) => file, - Err(e) => Err(LocationError::DotfileWriteFailure(e, path.to_string()))?, - }; - - match fs::remove_file(format!("{}/{}", path.clone(), ".spacewrite")) { - Ok(_) => (), - Err(e) => Err(LocationError::DotfileWriteFailure(e, path.to_string()))?, - } - } else { - // unix allows us to test this more directly - match fs::File::open(&path) { - Ok(_) => println!("Path is valid, creating location for '{}'", &path), - Err(e) => Err(LocationError::FileReadError(e))?, - } + if metadata(path) + .await + .map_err(|e| LocationError::DotfileReadFailure(e, path.to_owned()))? + .permissions() + .readonly() + { + return Err(LocationError::ReadonlyDotFileLocationFailure(path.to_owned()).into()); } + let path_string = path.to_string_lossy().to_string(); + // check if location already exists - let location = match db + let location_resource = if let Some(location) = ctx + .database .location() - .find_first(vec![location::local_path::equals(Some(path.to_string()))]) + .find_first(vec![location::local_path::equals(Some( + path_string.clone(), + ))]) .exec() .await? { - Some(location) => location, - None => { - println!( - "Location does not exist, creating new location for '{}'", - &path - ); - let uuid = uuid::Uuid::new_v4(); + location.into() + } else { + info!( + "Location does not exist, creating new location for '{:#?}'", + path + ); + let uuid = Uuid::new_v4(); - let p = Path::new(&path); + let config = get_nodestate(); - let location = db - .location() - .create( - location::pub_id::set(uuid.to_string()), - vec![ - location::name::set(Some( - p.file_name().unwrap().to_string_lossy().to_string(), - )), - location::is_online::set(true), - location::local_path::set(Some(path.to_string())), - location::node_id::set(Some(config.node_id)), - ], - ) - .exec() - .await?; + let location = ctx + .database + .location() + .create( + location::pub_id::set(uuid.to_string()), + vec![ + location::name::set(Some( + path.file_name().unwrap().to_string_lossy().to_string(), + )), + location::is_online::set(true), + location::local_path::set(Some(path_string)), + location::node_id::set(Some(config.node_id)), + ], + ) + .exec() + .await?; - println!("Created location: {:?}", location); + info!("Created location: {:?}", location); - // write a file called .spacedrive to path containing the location id in JSON format - let mut dotfile = match fs::File::create(format!("{}/{}", path.clone(), DOTFILE_NAME)) { - Ok(file) => file, - Err(e) => Err(LocationError::DotfileWriteFailure(e, path.to_string()))?, - }; + // write a file called .spacedrive to path containing the location id in JSON format + let mut dotfile = File::create(path.with_file_name(DOTFILE_NAME)) + .await + .map_err(|e| LocationError::DotfileWriteFailure(e, path.to_owned()))?; - let data = DotSpacedrive { - location_uuid: uuid.to_string(), - library_uuid: config.current_library_uuid, - }; + let data = DotSpacedrive { + location_uuid: uuid, + library_uuid: config.current_library_uuid, + }; - let json = match serde_json::to_string(&data) { - Ok(json) => json, - Err(e) => Err(LocationError::DotfileSerializeFailure(e, path.to_string()))?, - }; + let json_bytes = serde_json::to_vec(&data) + .map_err(|e| LocationError::DotfileSerializeFailure(e, path.to_owned()))?; - match dotfile.write_all(json.as_bytes()) { - Ok(_) => (), - Err(e) => Err(LocationError::DotfileWriteFailure(e, path.to_string()))?, - } + dotfile + .write_all(&json_bytes) + .await + .map_err(|e| LocationError::DotfileWriteFailure(e, path.to_owned()))?; - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::SysGetLocations)) - .await; + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::SysGetLocations)) + .await; - location - } + location.into() }; - Ok(location.into()) + Ok(location_resource) } pub async fn delete_location(ctx: &CoreContext, location_id: i32) -> Result<(), SysError> { - let db = &ctx.database; - - db.file_path() + ctx.database + .file_path() .find_many(vec![file_path::location_id::equals(Some(location_id))]) .delete() .exec() .await?; - db.location() + ctx.database + .location() .find_unique(location::id::equals(location_id)) .delete() .exec() @@ -238,7 +233,7 @@ pub async fn delete_location(ctx: &CoreContext, location_id: i32) -> Result<(), ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::SysGetLocations)) .await; - println!("Location {} deleted", location_id); + info!("Location {} deleted", location_id); Ok(()) } @@ -246,15 +241,21 @@ pub async fn delete_location(ctx: &CoreContext, location_id: i32) -> Result<(), #[derive(Error, Debug)] pub enum LocationError { #[error("Failed to create location (uuid {uuid:?})")] - CreateFailure { uuid: String }, - #[error("Failed to read location dotfile")] - DotfileReadFailure(io::Error), + CreateFailure { uuid: Uuid }, + #[error("Failed to read location dotfile (path: {1:?})")] + DotfileReadFailure(io::Error, PathBuf), #[error("Failed to serialize dotfile for location (at path: {1:?})")] - DotfileSerializeFailure(serde_json::Error, String), - #[error("Location not found (uuid: {1:?})")] - DotfileWriteFailure(io::Error, String), - #[error("Location not found (uuid: {0:?})")] - NotFound(String), + DotfileSerializeFailure(serde_json::Error, PathBuf), + #[error("Dotfile location is read only (at path: {0:?})")] + ReadonlyDotFileLocationFailure(PathBuf), + #[error("Failed to write dotfile (path: {1:?})")] + DotfileWriteFailure(io::Error, PathBuf), + #[error("Location not found (path: {0:?})")] + PathNotFound(PathBuf), + #[error("Location not found (uuid: {0})")] + UuidNotFound(Uuid), + #[error("Location not found (id: {0})")] + IdNotFound(i32), #[error("Failed to open file from local os")] FileReadError(io::Error), #[error("Failed to read mounted volumes from local os")] diff --git a/core/src/sys/mod.rs b/core/src/sys/mod.rs index d9565e399..8eafbf28b 100644 --- a/core/src/sys/mod.rs +++ b/core/src/sys/mod.rs @@ -11,11 +11,11 @@ use crate::{job, prisma}; #[derive(Error, Debug)] pub enum SysError { #[error("Location error")] - LocationError(#[from] LocationError), + Location(#[from] LocationError), #[error("Error with system volumes")] - VolumeError(String), + Volume(String), #[error("Error from job runner")] - JobError(#[from] job::JobError), + Job(#[from] job::JobError), #[error("Database error")] - DatabaseError(#[from] prisma::QueryError), + Database(#[from] prisma::QueryError), } diff --git a/core/src/sys/volumes.rs b/core/src/sys/volumes.rs index 6a043d991..ffb235c05 100644 --- a/core/src/sys/volumes.rs +++ b/core/src/sys/volumes.rs @@ -1,5 +1,5 @@ // use crate::native; -use crate::{node::get_nodestate, prisma::volume::*}; +use crate::{node::get_nodestate, prisma::volume::*, CoreContext}; use serde::{Deserialize, Serialize}; use ts_rs::TS; // #[cfg(not(target_os = "macos"))] @@ -7,8 +7,6 @@ use std::process::Command; // #[cfg(not(target_os = "macos"))] use sysinfo::{DiskExt, System, SystemExt}; -use crate::CoreContext; - use super::SysError; #[derive(Serialize, Deserialize, Debug, Default, Clone, TS)] @@ -27,17 +25,17 @@ pub struct Volume { impl Volume { pub async fn save(ctx: &CoreContext) -> Result<(), SysError> { - let db = &ctx.database; let config = get_nodestate(); let volumes = Self::get_volumes()?; // enter all volumes associate with this client add to db for volume in volumes { - db.volume() + ctx.database + .volume() .upsert( node_id_mount_point_name( - config.node_id.clone(), + config.node_id, volume.mount_point.to_string(), volume.name.to_string(), ), @@ -67,7 +65,7 @@ impl Volume { Ok(()) } pub fn get_volumes() -> Result, SysError> { - let all_volumes: Vec = System::new_all() + Ok(System::new_all() .disks() .iter() .map(|disk| { @@ -123,15 +121,8 @@ impl Volume { is_root_filesystem: mount_point == "/", } }) - .collect(); - - let volumes = all_volumes - .clone() - .into_iter() .filter(|volume| !volume.mount_point.starts_with("/System")) - .collect(); - - Ok(volumes) + .collect()) } } diff --git a/core/src/util/db.rs b/core/src/util/db.rs index d0b31b2c8..e299c7b94 100644 --- a/core/src/util/db.rs +++ b/core/src/util/db.rs @@ -2,10 +2,13 @@ use crate::prisma::{self, migration, PrismaClient}; use crate::CoreContext; use data_encoding::HEXLOWER; use include_dir::{include_dir, Dir}; +use log::{error, info}; use prisma_client_rust::raw; use ring::digest::{Context, Digest, SHA256}; use std::ffi::OsStr; +use std::fmt::Debug; use std::io::{self, BufReader, Read}; +use std::path::Path; use thiserror::Error; const INIT_MIGRATION: &str = include_str!("../../prisma/migrations/migration_table/migration.sql"); @@ -17,9 +20,12 @@ pub enum DatabaseError { ClientError(#[from] prisma::NewClientError), } -pub async fn create_connection(path: &str) -> Result { - println!("Creating database connection: {:?}", path); - let client = prisma::new_client_with_url(&format!("file:{}", &path)).await?; +pub async fn create_connection( + path: impl AsRef + Debug, +) -> Result { + info!("Creating database connection: {:?}", path); + let client = + prisma::new_client_with_url(&format!("file:{}", path.as_ref().to_string_lossy())).await?; Ok(client) } @@ -47,12 +53,12 @@ pub async fn run_migrations(ctx: &CoreContext) -> Result<(), DatabaseError> { .await { Ok(data) => { - if data.len() == 0 { + if data.is_empty() { // execute migration match client._execute_raw(raw!(INIT_MIGRATION)).await { Ok(_) => {} Err(e) => { - println!("Failed to create migration table: {}", e); + info!("Failed to create migration table: {}", e); } }; @@ -64,7 +70,7 @@ pub async fn run_migrations(ctx: &CoreContext) -> Result<(), DatabaseError> { .unwrap(); #[cfg(debug_assertions)] - println!("Migration table created: {:?}", value); + info!("Migration table created: {:?}", value); } let mut migration_subdirs = MIGRATIONS_DIR @@ -89,7 +95,7 @@ pub async fn run_migrations(ctx: &CoreContext) -> Result<(), DatabaseError> { }); for subdir in migration_subdirs { - println!("{:?}", subdir.path()); + info!("{:?}", subdir.path()); let migration_file = subdir .get_file(subdir.path().join("./migration.sql")) .unwrap(); @@ -110,9 +116,9 @@ pub async fn run_migrations(ctx: &CoreContext) -> Result<(), DatabaseError> { if existing_migration.is_none() { #[cfg(debug_assertions)] - println!("Running migration: {}", name); + info!("Running migration: {}", name); - let steps = migration_sql.split(";").collect::>(); + let steps = migration_sql.split(';').collect::>(); let steps = &steps[0..steps.len() - 1]; client @@ -138,15 +144,15 @@ pub async fn run_migrations(ctx: &CoreContext) -> Result<(), DatabaseError> { .unwrap(); } Err(e) => { - println!("Error running migration: {}", name); - println!("{}", e); + error!("Error running migration: {}", name); + error!("{:?}", e); break; } } } #[cfg(debug_assertions)] - println!("Migration {} recorded successfully", name); + info!("Migration {} recorded successfully", name); } } } From b96944a346c94488f8578082c10377da7b948dc9 Mon Sep 17 00:00:00 2001 From: Ericson Fogo Soares Date: Wed, 29 Jun 2022 14:46:47 -0300 Subject: [PATCH 02/51] Removing tokio::spawn_blocking from thumbnail generation --- core/src/encode/thumb.rs | 117 ++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/core/src/encode/thumb.rs b/core/src/encode/thumb.rs index d9d23d587..989859506 100644 --- a/core/src/encode/thumb.rs +++ b/core/src/encode/thumb.rs @@ -4,10 +4,10 @@ use crate::{ prisma::file_path, sys, CoreContext, CoreEvent, }; -use futures::executor::block_on; use image::{self, imageops, DynamicImage, GenericImageView}; use log::{error, info}; use std::error::Error; +use std::ops::Deref; use std::path::{Path, PathBuf}; use tokio::fs; use webp::Encoder; @@ -55,72 +55,62 @@ impl Job for ThumbnailJob { let image_files = get_images(&ctx.core_ctx, self.location_id, &self.path).await?; info!("Found {:?} files", image_files.len()); - let is_background = self.background; + ctx.progress(vec![ + JobReportUpdate::TaskCount(image_files.len()), + JobReportUpdate::Message(format!("Preparing to process {} files", image_files.len())), + ]); - tokio::task::spawn_blocking(move || { - ctx.progress(vec![ - JobReportUpdate::TaskCount(image_files.len()), - JobReportUpdate::Message(format!( - "Preparing to process {} files", - image_files.len() - )), - ]); + for (i, image_file) in image_files.iter().enumerate() { + ctx.progress(vec![JobReportUpdate::Message(format!( + "Processing {}", + image_file.materialized_path.clone() + ))]); - for (i, image_file) in image_files.iter().enumerate() { - ctx.progress(vec![JobReportUpdate::Message(format!( - "Processing {}", - image_file.materialized_path.clone() - ))]); + // assemble the file path + let path = Path::new(&root_path).join(&image_file.materialized_path); + error!("image_file {:?}", image_file); - // assemble the file path - let path = Path::new(&root_path).join(&image_file.materialized_path); - error!("image_file {:?}", image_file); - - // get cas_id, if none found skip - let cas_id = match image_file.file() { - Ok(file) => { - if let Some(f) = file { - f.cas_id.clone() - } else { - continue; - } - } - Err(_) => { - error!("Error getting cas_id {:?}", image_file.materialized_path); + // get cas_id, if none found skip + let cas_id = match image_file.file() { + Ok(file) => { + if let Some(f) = file { + f.cas_id.clone() + } else { continue; } - }; - - // Define and write the WebP-encoded file to a given path - let output_path = config - .data_path - .as_ref() - .unwrap() - .join(THUMBNAIL_CACHE_DIR_NAME) - .join(format!("{}", location.id)) - .join(&cas_id) - .with_extension("webp"); - - // check if file exists at output path - if !output_path.exists() { - info!("Writing {:?} to {:?}", path, output_path); - block_on(generate_thumbnail(&path, &output_path)) - .map_err(|e| { - info!("Error generating thumb {:?}", e); - }) - .unwrap_or(()); - - ctx.progress(vec![JobReportUpdate::CompletedTaskCount(i + 1)]); - - if !is_background { - block_on(ctx.core_ctx.emit(CoreEvent::NewThumbnail { cas_id })); - }; - } else { - info!("Thumb exists, skipping... {}", output_path.display()); } + Err(_) => { + error!("Error getting cas_id {:?}", image_file.materialized_path); + continue; + } + }; + + // Define and write the WebP-encoded file to a given path + let output_path = config + .data_path + .as_ref() + .unwrap() + .join(THUMBNAIL_CACHE_DIR_NAME) + .join(format!("{}", location.id)) + .join(&cas_id) + .with_extension("webp"); + + // check if file exists at output path + if !output_path.exists() { + info!("Writing {:?} to {:?}", path, output_path); + if let Err(e) = generate_thumbnail(&path, &output_path).await { + error!("Error generating thumb {:?}", e); + } + + ctx.progress(vec![JobReportUpdate::CompletedTaskCount(i + 1)]); + + if !self.background { + ctx.core_ctx.emit(CoreEvent::NewThumbnail { cas_id }).await; + }; + } else { + info!("Thumb exists, skipping... {}", output_path.display()); } - }) - .await?; + } Ok(()) } @@ -144,9 +134,12 @@ pub async fn generate_thumbnail>( let encoder = Encoder::from_image(&img)?; // Encode the image at a specified quality 0-100 - let webp = encoder.encode(THUMBNAIL_QUALITY); - fs::write(output_path, &*webp).await?; + // Type WebPMemory is !Send, which makes the Future in this function !Send, + // this make us `deref` to have a `&[u8]` and then `to_owned` to make a Vec + // which implies on a unwanted clone... + let webp = encoder.encode(THUMBNAIL_QUALITY).deref().to_owned(); + fs::write(output_path, &webp).await?; Ok(()) } From b692426db4bd88dc8dfad50da70abb4dbb8792dd Mon Sep 17 00:00:00 2001 From: voletro <65332602+voletro@users.noreply.github.com> Date: Thu, 30 Jun 2022 11:44:14 +1000 Subject: [PATCH 03/51] Rewrite of setup-system.ps1 This is a full rewrite of setup-system.ps1 that sets up a users machine for Spacedrive development. The CONTRIBUTING.md also includes info on this file. --- .github/scripts/setup-system.ps1 | 168 +++++++++++++++++++++++++++++-- CONTRIBUTING.md | 2 + 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/.github/scripts/setup-system.ps1 b/.github/scripts/setup-system.ps1 index 9a8a206b5..786cdd824 100644 --- a/.github/scripts/setup-system.ps1 +++ b/.github/scripts/setup-system.ps1 @@ -1,10 +1,160 @@ -Write-Host "This script is currently being used by CI and will need some more work before anyone can use it like the 'setup-system.sh' script for macOS and Linux!" +# Get temp folder +$temp = [System.IO.Path]::GetTempPath() -$VCINSTALLDIR = $(& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath) -Add-Content $env:GITHUB_ENV "LIBCLANG_PATH=${VCINSTALLDIR}\VC\Tools\LLVM\x64\bin`n" -Invoke-WebRequest "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full-shared.7z" -OutFile ffmpeg-release-full-shared.7z -7z x ffmpeg-release-full-shared.7z -mkdir ffmpeg -mv ffmpeg-*/* ffmpeg/ -Add-Content $env:GITHUB_ENV "FFMPEG_DIR=${pwd}\ffmpeg`n" -Add-Content $env:GITHUB_PATH "${pwd}\ffmpeg\bin`n" \ No newline at end of file +# Get current running dir +$currentLocation = $((Get-Location).path) + +$Host.UI.RawUI.WindowTitle = "Spacedrive DE Setup (Administrator)" +$Host.UI.RawUI.BackgroundColor = "Black" + +# Check to see if a command exists (eg if an app is installed) +Function CheckCommand { + + Param ($command) + + $oldPreference = $ErrorActionPreference + + $ErrorActionPreference = 'stop' + + try { if (Get-Command $command) { RETURN $true } } + + Catch { RETURN $false } + + Finally { $ErrorActionPreference = $oldPreference } + +} + +Clear-Host + +Write-Host "Spacedrive Development Environment Setup" -ForegroundColor Magenta +Write-Host @" + +To set up your machine for Spacedrive development, this script will do the following: + +1) Check for Rust and Cargo + +2) Install pnpm (if not installed) + +3) Install the latest version of Node.js using pnpm + +4) Install LLVM (compiler for ffmpeg-rust) + +4) Download ffmpeg and set as an environment variable + +"@ + +Write-Host "Press the Enter key to begin, or press CTRL + C to stop." -ForegroundColor Gray + +Read-Host + +Write-Host "Checking for Rust and Cargo..." -ForegroundColor Yellow +Start-Sleep -Milliseconds 150 + +$cargoCheck = CheckCommand cargo + +if ($cargoCheck -eq $false) { + Write-Host @" +Cargo is not installed. + +To use Spacedrive on Windows, Cargo needs to be installed. +The Visual Studio C++ Build tools are also required. +Instructions can be found here: + +https://tauri.app/v1/guides/getting-started/prerequisites/#setting-up-windows + +Once you have installed Cargo, re-run this script. + +"@ + Write-Host "Press the Enter key to close." -ForegroundColor Gray + Read-Host + Exit +} +else { + Write-Host "Cargo is installed." +} + +Write-Host +Write-Host "Checking for pnpm..." -ForegroundColor Yellow +Start-Sleep -Milliseconds 150 + +$pnpmCheck = CheckCommand pnpm +if ($pnpmCheck -eq $false) { + + Write-Host "pnpm is not installed. Installing now." + Write-Host "Running the pnpm installer..." + + #pnpm installer taken from https://pnpm.io + Invoke-WebRequest https://get.pnpm.io/install.ps1 -useb | Invoke-Expression + + # Reset the PATH env variables to make sure pnpm is accessible + $env:PNPM_HOME = [System.Environment]::GetEnvironmentVariable("PNPM_HOME", "User") + $env:Path = [System.Environment]::ExpandEnvironmentVariables([System.Environment]::GetEnvironmentVariable("Path", "User")) + +} +else { + Write-Host "pnpm is installed." +} + +Write-Host +Write-Host "Using pnpm to install the latest version of Node..." -ForegroundColor Yellow +Write-Host "This will set your global Node version to the latest!" +Start-Sleep -Milliseconds 150 + +# Runs the pnpm command to use the latest version of node, which also installs it +Start-Process -Wait -FilePath "pnpm" -ArgumentList "env use --global latest" -PassThru -Verb runAs + +Write-Host +Write-Host "Downloading the LLVM installer..." -ForegroundColor Yellow +# Downloads latest installer for LLVM +$filenamePattern = "*-win64.exe" +$releasesUri = "https://api.github.com/repos/llvm/llvm-project/releases/latest" +$downloadUri = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).browser_download_url + +Start-BitsTransfer -Source $downloadUri -Destination "$temp\llvm.exe" + +Write-Host +Write-Host "Running the LLVM installer..." -ForegroundColor Yellow +Write-Host "Please follow the instructions to install LLVM." +Write-Host "Ensure you add LLVM to your PATH." + +Start-Process "$temp\llvm.exe" -Wait + +Write-Host +Write-Host "Downloading the latest ffmpeg build..." -ForegroundColor Yellow + +# Downloads the latest shared build of ffmpeg from GitHub +$filenamePattern = "*-full_build-shared.zip" +$releasesUri = "https://api.github.com/repos/GyanD/codexffmpeg/releases/latest" +$downloadUri = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).browser_download_url +$filename = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).name +$remove = ".zip" +$foldername = $filename.Substring(0, ($filename.Length - $remove.Length)) + +Start-BitsTransfer -Source $downloadUri -Destination "$temp\ffmpeg.zip" + +Write-Host +Write-Host "Expanding ffmpeg zip..." -ForegroundColor Yellow + +Expand-Archive "$temp\ffmpeg.zip" $HOME -ErrorAction SilentlyContinue + +Remove-Item "$temp\ffmpeg.zip" + +Write-Host +Write-Host "Setting environment variables..." -ForegroundColor Yellow + +# Sets environment variable for ffmpeg +[System.Environment]::SetEnvironmentVariable('FFMPEG_DIR', "$HOME\$foldername", [System.EnvironmentVariableTarget]::User) + +Write-Host +Write-Host "Copying Required .dll files..." -ForegroundColor Yellow + +# Create target\debug folder, continue if already exists +New-Item -Path $currentLocation\target\debug -ItemType Directory -ErrorAction SilentlyContinue + +# Copies all .dll required for rust-ffmpeg to target\debug folder +Get-ChildItem "$HOME\$foldername\bin" -recurse -filter *.dll | Copy-Item -Destination "$currentLocation\target\debug" + +Write-Host +Write-Host "Your machine has been setup for Spacedrive development!" +Write-Host "Press the Enter key to close." -ForegroundColor Gray +Read-Host \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 942c66d63..7c7f383f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,8 @@ This project uses [Cargo](https://doc.rust-lang.org/cargo/getting-started/instal - `$ cd spacedrive` - For Linux or MacOS users run: `./.github/scripts/setup-system.sh` - This will install FFMPEG and any other required dependencies for Spacedrive to build. +- For Windows users run using PowerShell: `.\.github\scripts\setup-system.ps1` + - This will install pnpm, LLVM, FFMPEG and any other required dependencies for Spacedrive to build. - `$ pnpm i` - `$ pnpm prep` - Runs all necessary codegen & builds required dependencies. From f06911e08a211aa9307a96f4443e81f2d1683b9d Mon Sep 17 00:00:00 2001 From: Ericson Fogo Soares Date: Thu, 30 Jun 2022 00:04:37 -0300 Subject: [PATCH 04/51] Removing tokio::spawn_blocking from identifier job * Some small optimizations to await many queries concurrently --- core/src/file/cas/identifier.rs | 248 +++++++++++++++++--------------- 1 file changed, 131 insertions(+), 117 deletions(-) diff --git a/core/src/file/cas/identifier.rs b/core/src/file/cas/identifier.rs index de9a54483..855352385 100644 --- a/core/src/file/cas/identifier.rs +++ b/core/src/file/cas/identifier.rs @@ -8,11 +8,11 @@ use crate::{ CoreContext, }; use chrono::{DateTime, FixedOffset}; -use futures::executor::block_on; -use log::info; +use futures::future::join_all; +use log::{error, info}; use prisma_client_rust::{prisma_models::PrismaValue, raw, raw::Raw, Direction}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::error::Error; use std::path::{Path, PathBuf}; use tokio::{fs, io}; @@ -50,138 +50,154 @@ impl Job for FileIdentifierJob { // update job with total task count based on orphan file_paths count ctx.progress(vec![JobReportUpdate::TaskCount(task_count)]); - let db = ctx.core_ctx.database.clone(); - // dedicated tokio thread for task - let _ctx = tokio::task::spawn_blocking(move || { - let mut completed: usize = 0; - let mut cursor: i32 = 1; - // loop until task count is complete - while completed < task_count { - // link file_path ids to a CreateFile struct containing unique file data - let mut chunk: HashMap = HashMap::new(); - let mut cas_lookup: HashMap = HashMap::new(); + let mut completed: usize = 0; + let mut cursor: i32 = 1; + // loop until task count is complete + while completed < task_count { + // link file_path ids to a CreateFile struct containing unique file data + let mut chunk: HashMap = HashMap::new(); + let mut cas_lookup: HashMap = HashMap::new(); - // get chunk of orphans to process - let file_paths = match block_on(get_orphan_file_paths(&ctx.core_ctx, cursor)) { - Ok(file_paths) => file_paths, + // get chunk of orphans to process + let file_paths = match get_orphan_file_paths(&ctx.core_ctx, cursor).await { + Ok(file_paths) => file_paths, + Err(e) => { + info!("Error getting orphan file paths: {:#?}", e); + continue; + } + }; + info!( + "Processing {:?} orphan files. ({} completed of {})", + file_paths.len(), + completed, + task_count + ); + + // analyze each file_path + for file_path in &file_paths { + // get the cas_id and extract metadata + match prepare_file(&location_path, file_path).await { + Ok(file) => { + let cas_id = file.cas_id.clone(); + // create entry into chunks for created file data + chunk.insert(file_path.id, file); + cas_lookup.insert(cas_id, file_path.id); + } Err(e) => { - info!("Error getting orphan file paths: {:#?}", e); + info!("Error processing file: {:#?}", e); continue; } }; - info!( - "Processing {:?} orphan files. ({} completed of {})", - file_paths.len(), - completed, - task_count - ); + } - // analyze each file_path - for file_path in file_paths.iter() { - // get the cas_id and extract metadata - match block_on(prepare_file(&location_path, file_path)) { - Ok(file) => { - let cas_id = file.cas_id.clone(); - // create entry into chunks for created file data - chunk.insert(file_path.id, file); - cas_lookup.insert(cas_id, file_path.id); - } - Err(e) => { - info!("Error processing file: {:#?}", e); - continue; - } - }; + // find all existing files by cas id + let generated_cas_ids = chunk.values().map(|c| c.cas_id.clone()).collect(); + let existing_files = ctx + .core_ctx + .database + .file() + .find_many(vec![file::cas_id::in_vec(generated_cas_ids)]) + .exec() + .await?; + + info!("Found {} existing files", existing_files.len()); + + // link those existing files to their file paths + // Had to put the file_path in a variable outside of the closure, to satisfy the borrow checker + let prisma_file_path = ctx.core_ctx.database.file_path(); + for result in join_all(existing_files.iter().map(|file| { + prisma_file_path + .find_unique(file_path::id::equals( + *cas_lookup.get(&file.cas_id).unwrap(), + )) + .update(vec![file_path::file_id::set(Some(file.id))]) + .exec() + })) + .await + { + if let Err(e) = result { + error!("Error linking file: {:#?}", e); } + } - // find all existing files by cas id - let generated_cas_ids = chunk.values().map(|c| c.cas_id.clone()).collect(); - let existing_files: Vec = block_on( - db.file() - .find_many(vec![file::cas_id::in_vec(generated_cas_ids)]) - .exec(), - ) - .unwrap(); - info!("Found {} existing files", existing_files.len()); + let existing_files_cas_ids = existing_files + .iter() + .map(|file| file.cas_id.clone()) + .collect::>(); - // link those existing files to their file paths - for file in existing_files.iter() { - let file_path_id = cas_lookup.get(&file.cas_id).unwrap(); - block_on( - db.file_path() - .find_unique(file_path::id::equals(*file_path_id)) - .update(vec![file_path::file_id::set(Some(file.id))]) - .exec(), - ) - .unwrap(); - } + // extract files that don't already exist in the database + let new_files = chunk + .iter() + .map(|(_id, create_file)| create_file) + .filter(|create_file| !existing_files_cas_ids.contains(&create_file.cas_id)) + .collect::>(); - // extract files that don't already exist in the database - let new_files: Vec<&CreateFile> = chunk - .iter() - .map(|(_, c)| c) - .filter(|c| !existing_files.iter().any(|d| d.cas_id == c.cas_id)) - .collect(); + // assemble prisma values for new unique files + let mut values: Vec = Vec::new(); + for file in &new_files { + values.extend([ + PrismaValue::String(file.cas_id.clone()), + PrismaValue::Int(file.size_in_bytes), + PrismaValue::DateTime(file.date_created), + ]); + } - // assemble prisma values for new unique files - let mut values: Vec = Vec::new(); - for file in new_files.iter() { - values.extend([ - PrismaValue::String(file.cas_id.clone()), - PrismaValue::Int(file.size_in_bytes), - PrismaValue::DateTime(file.date_created), - ]); - } - - // create new file records with assembled values - let created_files: Vec = block_on(db._query_raw(Raw::new( + // create new file records with assembled values + let created_files: Vec = ctx + .core_ctx + .database + ._query_raw(Raw::new( &format!( "INSERT INTO files (cas_id, size_in_bytes, date_created) VALUES {} ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id", vec!["({}, {}, {})"; new_files.len()].join(",") ), values, - ))) + )) + .await .unwrap_or_else(|e| { - info!("Error inserting files: {:#?}", e); + error!("Error inserting files: {:#?}", e); Vec::new() }); + // This code is duplicates, is this right? + for result in join_all(created_files.iter().map(|file| { // associate newly created files with their respective file_paths - for file in created_files.iter() { - // TODO: this is potentially bottle necking the chunk system, individually linking file_path to file, 100 queries per chunk - // - insert many could work, but I couldn't find a good way to do this in a single SQL query - let file_path_id = cas_lookup.get(&file.cas_id).unwrap(); - block_on( - db.file_path() - .find_unique(file_path::id::equals(*file_path_id)) - .update(vec![file_path::file_id::set(Some(file.id))]) - .exec(), - ) - .unwrap(); + // TODO: this is potentially bottle necking the chunk system, individually linking file_path to file, 100 queries per chunk + // - insert many could work, but I couldn't find a good way to do this in a single SQL query + prisma_file_path + .find_unique(file_path::id::equals( + *cas_lookup.get(&file.cas_id).unwrap(), + )) + .update(vec![file_path::file_id::set(Some(file.id))]) + .exec() + })) + .await + { + if let Err(e) = result { + error!("Error linking file: {:#?}", e); } - - // handle loop end - let last_row = match file_paths.last() { - Some(l) => l, - None => { - break; - } - }; - cursor = last_row.id; - completed += 1; - - ctx.progress(vec![ - JobReportUpdate::CompletedTaskCount(completed), - JobReportUpdate::Message(format!( - "Processed {} of {} orphan files", - completed * CHUNK_SIZE, - total_count - )), - ]); } - ctx - }) - .await?; + + // handle loop end + let last_row = match file_paths.last() { + Some(l) => l, + None => { + break; + } + }; + cursor = last_row.id; + completed += 1; + + ctx.progress(vec![ + JobReportUpdate::CompletedTaskCount(completed), + JobReportUpdate::Message(format!( + "Processed {} of {} orphan files", + completed * CHUNK_SIZE, + total_count + )), + ]); + } // let _remaining = count_orphan_file_paths(&ctx.core_ctx, location.id.into()).await?; Ok(()) @@ -197,8 +213,7 @@ pub async fn count_orphan_file_paths( ctx: &CoreContext, location_id: i64, ) -> Result { - let db = &ctx.database; - let files_count = db + let files_count = ctx.database ._query_raw::(raw!( "SELECT COUNT(*) AS count FROM file_paths WHERE file_id IS NULL AND is_dir IS FALSE AND location_id = {}", PrismaValue::Int(location_id) @@ -211,12 +226,11 @@ pub async fn get_orphan_file_paths( ctx: &CoreContext, cursor: i32, ) -> Result, FileError> { - let db = &ctx.database; info!( "discovering {} orphan file paths at cursor: {:?}", CHUNK_SIZE, cursor ); - let files = db + ctx.database .file_path() .find_many(vec![ file_path::file_id::equals(None), @@ -226,8 +240,8 @@ pub async fn get_orphan_file_paths( .cursor(file_path::id::cursor(cursor)) .take(CHUNK_SIZE as i64) .exec() - .await?; - Ok(files) + .await + .map_err(|e| e.into()) } #[derive(Deserialize, Serialize, Debug)] From 65ad2bab7e274ac5ce7b3217f0f96f008d18f015 Mon Sep 17 00:00:00 2001 From: voletro <65332602+voletro@users.noreply.github.com> Date: Fri, 1 Jul 2022 10:38:41 +1000 Subject: [PATCH 05/51] Remove user input so it works with ci. --- .github/scripts/setup-system.ps1 | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/scripts/setup-system.ps1 b/.github/scripts/setup-system.ps1 index 786cdd824..99c8049e8 100644 --- a/.github/scripts/setup-system.ps1 +++ b/.github/scripts/setup-system.ps1 @@ -43,10 +43,6 @@ To set up your machine for Spacedrive development, this script will do the follo "@ -Write-Host "Press the Enter key to begin, or press CTRL + C to stop." -ForegroundColor Gray - -Read-Host - Write-Host "Checking for Rust and Cargo..." -ForegroundColor Yellow Start-Sleep -Milliseconds 150 @@ -65,8 +61,6 @@ https://tauri.app/v1/guides/getting-started/prerequisites/#setting-up-windows Once you have installed Cargo, re-run this script. "@ - Write-Host "Press the Enter key to close." -ForegroundColor Gray - Read-Host Exit } else { @@ -156,5 +150,3 @@ Get-ChildItem "$HOME\$foldername\bin" -recurse -filter *.dll | Copy-Item -Destin Write-Host Write-Host "Your machine has been setup for Spacedrive development!" -Write-Host "Press the Enter key to close." -ForegroundColor Gray -Read-Host \ No newline at end of file From 351a47ef30deeb526325e0f0cf93234424613fad Mon Sep 17 00:00:00 2001 From: he1d1 Date: Sun, 3 Jul 2022 12:59:51 +0100 Subject: [PATCH 06/51] Client tries to reconnect when sockets close --- apps/web/src/App.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 72560e3ff..36faa302d 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -3,7 +3,22 @@ import { ClientCommand, ClientQuery, CoreEvent } from '@sd/core'; import SpacedriveInterface from '@sd/interface'; import React, { useEffect } from 'react'; -const websocket = new WebSocket(import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/ws'); +const timeouts = [1, 2, 5, 10]; + +const startWebsocket = (timeoutIndex = 0) => { + const ws = new WebSocket(import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/ws'); + + ws.addEventListener('close', (event) => { + setTimeout( + () => startWebsocket(timeoutIndex++), + timeouts[timeoutIndex] ?? timeouts[timeouts.length - 1] + ); + }); + + return ws; +}; + +const websocket = startWebsocket(); const randomId = () => Math.random().toString(36).slice(2); From b783c32a04db59e3436d176ab26971d485df8616 Mon Sep 17 00:00:00 2001 From: voletro <65332602+voletro@users.noreply.github.com> Date: Tue, 5 Jul 2022 11:21:42 +1000 Subject: [PATCH 07/51] Added headless mode, added LLVM installer to ci. --- .github/scripts/setup-system.ps1 | 67 ++++++++++++++++++++------------ .github/workflows/ci.yml | 20 ++++++---- 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/.github/scripts/setup-system.ps1 b/.github/scripts/setup-system.ps1 index 99c8049e8..b2651a40c 100644 --- a/.github/scripts/setup-system.ps1 +++ b/.github/scripts/setup-system.ps1 @@ -1,12 +1,15 @@ +# Get ci parameter to check if running with ci +param( + [Parameter()] + [Switch]$ci +) + # Get temp folder $temp = [System.IO.Path]::GetTempPath() # Get current running dir $currentLocation = $((Get-Location).path) -$Host.UI.RawUI.WindowTitle = "Spacedrive DE Setup (Administrator)" -$Host.UI.RawUI.BackgroundColor = "Black" - # Check to see if a command exists (eg if an app is installed) Function CheckCommand { @@ -24,8 +27,6 @@ Function CheckCommand { } -Clear-Host - Write-Host "Spacedrive Development Environment Setup" -ForegroundColor Magenta Write-Host @" @@ -80,7 +81,7 @@ if ($pnpmCheck -eq $false) { #pnpm installer taken from https://pnpm.io Invoke-WebRequest https://get.pnpm.io/install.ps1 -useb | Invoke-Expression - # Reset the PATH env variables to make sure pnpm is accessible + # Reset the PATH env variables to make sure pnpm is accessible $env:PNPM_HOME = [System.Environment]::GetEnvironmentVariable("PNPM_HOME", "User") $env:Path = [System.Environment]::ExpandEnvironmentVariables([System.Environment]::GetEnvironmentVariable("Path", "User")) @@ -89,29 +90,47 @@ else { Write-Host "pnpm is installed." } -Write-Host -Write-Host "Using pnpm to install the latest version of Node..." -ForegroundColor Yellow -Write-Host "This will set your global Node version to the latest!" -Start-Sleep -Milliseconds 150 +# A GitHub Action takes care of installing node, so this isn't necessary if running in the ci. +if ($ci -eq $True) { + Write-Host + Write-Host "Running with Ci, skipping Node install." -ForegroundColor Yellow +} +else { + Write-Host + Write-Host "Using pnpm to install the latest version of Node..." -ForegroundColor Yellow + Write-Host "This will set your global Node version to the latest!" + Start-Sleep -Milliseconds 150 -# Runs the pnpm command to use the latest version of node, which also installs it -Start-Process -Wait -FilePath "pnpm" -ArgumentList "env use --global latest" -PassThru -Verb runAs + # Runs the pnpm command to use the latest version of node, which also installs it + Start-Process -Wait -FilePath "pnpm" -ArgumentList "env use --global latest" -PassThru -Verb runAs +} -Write-Host -Write-Host "Downloading the LLVM installer..." -ForegroundColor Yellow -# Downloads latest installer for LLVM -$filenamePattern = "*-win64.exe" -$releasesUri = "https://api.github.com/repos/llvm/llvm-project/releases/latest" -$downloadUri = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).browser_download_url -Start-BitsTransfer -Source $downloadUri -Destination "$temp\llvm.exe" -Write-Host -Write-Host "Running the LLVM installer..." -ForegroundColor Yellow -Write-Host "Please follow the instructions to install LLVM." -Write-Host "Ensure you add LLVM to your PATH." +# We can't run the installer if running in ci, so we install LLVM through a GitHub Action instead. +if ($ci -eq $True) { + Write-Host + Write-Host "Running with Ci, skipping LLVM install." -ForegroundColor Yellow +} +else { + Write-Host + Write-Host "Downloading the LLVM installer..." -ForegroundColor Yellow + # Downloads latest installer for LLVM + $filenamePattern = "*-win64.exe" + $releasesUri = "https://api.github.com/repos/llvm/llvm-project/releases/latest" + $downloadUri = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).browser_download_url + + Start-BitsTransfer -Source $downloadUri -Destination "$temp\llvm.exe" + + Write-Host + Write-Host "Running the LLVM installer..." -ForegroundColor Yellow + Write-Host "Please follow the instructions to install LLVM." + Write-Host "Ensure you add LLVM to your PATH." + + Start-Process "$temp\llvm.exe" -Wait +} + -Start-Process "$temp\llvm.exe" -Wait Write-Host Write-Host "Downloading the latest ffmpeg build..." -ForegroundColor Yellow diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 013354f45..d78582456 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: id: pnpm-cache run: | echo "::set-output name=pnpm_cache_dir::$(pnpm store path)" - + - uses: actions/cache@v3 name: Setup pnpm cache with: @@ -44,7 +44,7 @@ jobs: key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - + - name: Install pnpm dependencies run: pnpm --frozen-lockfile i @@ -81,7 +81,7 @@ jobs: with: version: 7 run_install: false - + - name: Install Rust stable uses: actions-rs/toolchain@v1 with: @@ -89,19 +89,25 @@ jobs: profile: minimal override: true components: rustfmt, rust-src - + - name: Cache Rust Dependencies uses: Swatinem/rust-cache@v1 with: sharedKey: core-v1-${{ hashFiles('**/Cargo.lock') }} + - name: Install LLVM and Clang + if: matrix.platform == 'windows-latest' + uses: KyleMayes/install-llvm-action@v1 + with: + version: '14' + - name: Run 'setup-system.sh' script if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'macos-latest' run: ./.github/scripts/setup-system.sh - + - name: Run 'setup-system.ps1' script if: matrix.platform == 'windows-latest' - run: ./.github/scripts/setup-system.ps1 + run: ./.github/scripts/setup-system.ps1 -ci - name: Get pnpm store directory id: pnpm-cache @@ -116,7 +122,7 @@ jobs: ${{ runner.os }}-pnpm-store- - name: Install pnpm dependencies run: pnpm --frozen-lockfile i - + - name: Cache Prisma codegen id: cache-prisma uses: actions/cache@v3 From 6ee4974068ab068b027ef55ff1c698e89d68cb4e Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Tue, 5 Jul 2022 23:55:44 +0800 Subject: [PATCH 08/51] remove Cargo frozen flag --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d78582456..b1ab6a535 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,13 +133,13 @@ jobs: - name: Generate Prisma client working-directory: core if: steps.cache-prisma.outputs.cache-hit != 'true' - run: cargo run --frozen -p prisma-cli --release -- generate + run: cargo run -p prisma-cli --release -- generate - name: Cargo fetch run: cargo fetch - name: Check Core - run: cargo check --frozen -p sdcore --release + run: cargo check -p sdcore --release - name: Bundle Desktop run: pnpm desktop tauri build @@ -147,7 +147,7 @@ jobs: - name: Build Server if: matrix.platform == 'ubuntu-latest' run: | - cargo build --frozen -p server --release + cargo build -p server --release cp ./target/release/server ./apps/server/server - name: Determine image name & tag From afd33b2d4c2e2490c5fe64f71763c3c5339ba1da Mon Sep 17 00:00:00 2001 From: voletro <65332602+voletro@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:28:28 +1000 Subject: [PATCH 09/51] Remove Action for LLVM, use action that is already built in. Set GH env variables. --- .github/scripts/setup-system.ps1 | 17 +++++++++++++---- .github/workflows/ci.yml | 6 ------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/scripts/setup-system.ps1 b/.github/scripts/setup-system.ps1 index b2651a40c..2ba78c9cf 100644 --- a/.github/scripts/setup-system.ps1 +++ b/.github/scripts/setup-system.ps1 @@ -107,11 +107,13 @@ else { -# We can't run the installer if running in ci, so we install LLVM through a GitHub Action instead. +# The ci has LLVM installed already, so we instead just set the env variables. if ($ci -eq $True) { Write-Host Write-Host "Running with Ci, skipping LLVM install." -ForegroundColor Yellow -} + + $VCINSTALLDIR = $(& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath) + Add-Content $env:GITHUB_ENV "LIBCLANG_PATH=${VCINSTALLDIR}\VC\Tools\LLVM\x64\bin`n" else { Write-Host Write-Host "Downloading the LLVM installer..." -ForegroundColor Yellow @@ -155,8 +157,15 @@ Remove-Item "$temp\ffmpeg.zip" Write-Host Write-Host "Setting environment variables..." -ForegroundColor Yellow -# Sets environment variable for ffmpeg -[System.Environment]::SetEnvironmentVariable('FFMPEG_DIR', "$HOME\$foldername", [System.EnvironmentVariableTarget]::User) +if ($ci -eq $True) { + # If running in ci, we need to use GITHUB_ENV and GITHUB_PATH instead of the normal PATH env variables, so we set them here + Add-Content $env:GITHUB_ENV "FFMPEG_DIR=$HOME\$foldername`n" + Add-Content $env:GITHUB_PATH "$HOME\$foldername\bin`n" +} +else { + # Sets environment variable for ffmpeg + [System.Environment]::SetEnvironmentVariable('FFMPEG_DIR', "$HOME\$foldername", [System.EnvironmentVariableTarget]::User) +} Write-Host Write-Host "Copying Required .dll files..." -ForegroundColor Yellow diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1ab6a535..d78d7b382 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,12 +95,6 @@ jobs: with: sharedKey: core-v1-${{ hashFiles('**/Cargo.lock') }} - - name: Install LLVM and Clang - if: matrix.platform == 'windows-latest' - uses: KyleMayes/install-llvm-action@v1 - with: - version: '14' - - name: Run 'setup-system.sh' script if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'macos-latest' run: ./.github/scripts/setup-system.sh From c14da1c00c58980c6399f09a6c0104cba5fc05e6 Mon Sep 17 00:00:00 2001 From: voletro <65332602+voletro@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:33:23 +1000 Subject: [PATCH 10/51] Add a close bracket (i'm dumb) --- .github/scripts/setup-system.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/scripts/setup-system.ps1 b/.github/scripts/setup-system.ps1 index 2ba78c9cf..29efe7a36 100644 --- a/.github/scripts/setup-system.ps1 +++ b/.github/scripts/setup-system.ps1 @@ -114,7 +114,8 @@ if ($ci -eq $True) { $VCINSTALLDIR = $(& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath) Add-Content $env:GITHUB_ENV "LIBCLANG_PATH=${VCINSTALLDIR}\VC\Tools\LLVM\x64\bin`n" -else { + +} else { Write-Host Write-Host "Downloading the LLVM installer..." -ForegroundColor Yellow # Downloads latest installer for LLVM From e496e42cbe785e4cf1cae3ee26ae9628865477e0 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Thu, 7 Jul 2022 12:50:43 +0800 Subject: [PATCH 11/51] fix Tauri Appimage icon --- apps/desktop/src-tauri/tauri.linux.conf.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/tauri.linux.conf.json b/apps/desktop/src-tauri/tauri.linux.conf.json index 51b5a339d..5fc781e7f 100644 --- a/apps/desktop/src-tauri/tauri.linux.conf.json +++ b/apps/desktop/src-tauri/tauri.linux.conf.json @@ -15,7 +15,13 @@ "active": true, "targets": "all", "identifier": "com.spacedrive.desktop", - "icon": ["icons/icon.icns"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], "resources": [], "externalBin": [], "copyright": "Spacedrive Technology Inc.", From b4c0cb68c4d206888b34abdfe1cc571ada146b67 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Sat, 9 Jul 2022 16:12:51 +0800 Subject: [PATCH 12/51] reconnect websocket on web - with jitter & exponential backoff --- apps/web/src/App.tsx | 62 +++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 36faa302d..7a8eab2a4 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,35 +1,47 @@ import { BaseTransport } from '@sd/client'; -import { ClientCommand, ClientQuery, CoreEvent } from '@sd/core'; +import { ClientCommand, ClientQuery } from '@sd/core'; import SpacedriveInterface from '@sd/interface'; import React, { useEffect } from 'react'; -const timeouts = [1, 2, 5, 10]; - -const startWebsocket = (timeoutIndex = 0) => { - const ws = new WebSocket(import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/ws'); - - ws.addEventListener('close', (event) => { - setTimeout( - () => startWebsocket(timeoutIndex++), - timeouts[timeoutIndex] ?? timeouts[timeouts.length - 1] - ); - }); - - return ws; -}; - -const websocket = startWebsocket(); +const timeouts = [1000, 2000, 5000, 10000]; // In milliseconds const randomId = () => Math.random().toString(36).slice(2); // bind state to core via Tauri class Transport extends BaseTransport { + websocket: WebSocket; requestMap = new Map void>(); constructor() { super(); + this.websocket = new WebSocket( + import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/ws' + ); + this.attachEventListeners(); + } - websocket.addEventListener('message', (event) => { + async reconnect(timeoutIndex = 0) { + let timeout = + (timeouts[timeoutIndex] ?? timeouts[timeouts.length - 1]) + + (Math.floor(Math.random() * 5000 /* 5 Seconds */) + 1); + + setTimeout(() => { + let ws = new WebSocket(import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/ws'); + new Promise(function (resolve, reject) { + ws.addEventListener('open', () => resolve(null)); + ws.addEventListener('close', reject); + }) + .then(() => { + this.websocket = ws; + this.attachEventListeners(); + console.log('Reconnected!'); + }) + .catch((err) => this.reconnect(timeoutIndex++)); + }, timeout); + } + + attachEventListeners() { + this.websocket.addEventListener('message', (event) => { if (!event.data) return; const { id, payload } = JSON.parse(event.data); @@ -44,7 +56,13 @@ class Transport extends BaseTransport { } } }); + + this.websocket.addEventListener('close', () => { + console.log('GONE'); + this.reconnect(); + }); } + async query(query: ClientQuery) { const id = randomId(); let resolve: (data: any) => void; @@ -56,7 +74,7 @@ class Transport extends BaseTransport { // @ts-ignore this.requestMap.set(id, resolve); - websocket.send(JSON.stringify({ id, payload: { type: 'query', data: query } })); + this.websocket.send(JSON.stringify({ id, payload: { type: 'query', data: query } })); return await promise; } @@ -71,12 +89,14 @@ class Transport extends BaseTransport { // @ts-ignore this.requestMap.set(id, resolve); - websocket.send(JSON.stringify({ id, payload: { type: 'command', data: command } })); + this.websocket.send(JSON.stringify({ id, payload: { type: 'command', data: command } })); return await promise; } } +const transport = new Transport(); + function App() { useEffect(() => { window.parent.postMessage('spacedrive-hello', '*'); @@ -87,7 +107,7 @@ function App() { {/*
*/} Date: Mon, 11 Jul 2022 10:05:24 +0800 Subject: [PATCH 13/51] Library manager (#258) --- Cargo.lock | Bin 167334 -> 168055 bytes apps/desktop/src-tauri/src/main.rs | 23 +- apps/server/k8s/infrastructure.yaml | 42 -- apps/server/k8s/sdserver.yaml | 118 ----- apps/server/src/main.rs | 29 +- apps/web/src/App.tsx | 10 + core/Cargo.toml | 3 +- core/bindings/ClientCommand.ts | 3 +- core/bindings/ClientQuery.ts | 3 +- core/bindings/ConfigMetadata.ts | 3 + core/bindings/CoreResponse.ts | 3 +- core/bindings/LibraryCommand.ts | 3 + core/bindings/LibraryConfig.ts | 3 + core/bindings/LibraryConfigWrapped.ts | 4 + core/bindings/LibraryQuery.ts | 3 + core/bindings/NodeConfig.ts | 3 + core/bindings/NodeState.ts | 3 +- core/index.ts | 6 + .../migration.sql | 29 ++ core/prisma/schema.prisma | 16 +- core/src/encode/thumb.rs | 39 +- core/src/file/cas/identifier.rs | 25 +- core/src/file/explorer/open.rs | 16 +- core/src/file/indexer/mod.rs | 7 +- core/src/file/indexer/scan.rs | 15 +- core/src/file/mod.rs | 33 +- core/src/job/jobs.rs | 114 +++-- core/src/job/worker.rs | 65 ++- core/src/lib.rs | 464 +++++++++--------- core/src/library/library_config.rs | 72 +++ core/src/library/library_ctx.rs | 46 ++ core/src/library/library_manager.rs | 271 ++++++++++ core/src/library/loader.rs | 99 ---- core/src/library/mod.rs | 8 +- core/src/library/statistics.rs | 74 +-- core/src/node/config.rs | 149 ++++++ core/src/node/mod.rs | 75 +-- core/src/node/state.rs | 107 ---- core/src/sys/locations.rs | 63 +-- core/src/sys/volumes.rs | 16 +- core/src/util/db.rs | 220 ++++----- packages/client/package.json | 20 +- packages/client/src/bridge.ts | 75 ++- .../src/context}/AppPropsContext.tsx | 0 packages/client/src/context/index.ts | 1 + packages/client/src/files/index.ts | 2 - packages/client/src/files/query.ts | 21 - packages/client/src/files/state.ts | 23 - packages/client/src/hooks/index.ts | 1 + packages/client/src/hooks/useCoreEvents.tsx | 59 +++ packages/client/src/index.ts | 4 +- packages/client/src/stores/index.ts | 4 + .../src/stores/useExplorerStore.ts} | 8 +- .../src/stores/useInspectorStore.ts} | 9 +- packages/client/src/stores/useLibraryStore.ts | 67 +++ packages/interface/package.json | 3 +- packages/interface/src/App.tsx | 4 +- packages/interface/src/AppLayout.tsx | 2 +- packages/interface/src/AppRouter.tsx | 105 ++-- packages/interface/src/NotFound.tsx | 2 +- .../src/components/file/FileList.tsx | 14 +- .../src/components/file/FileThumb.tsx | 2 +- .../src/components/file/Inspector.tsx | 4 +- .../interface/src/components/file/Sidebar.tsx | 90 ++-- .../interface/src/components/layout/Card.tsx | 15 + .../src/components/layout/Dialog.tsx | 8 +- .../src/components/layout/TopBar.tsx | 10 +- .../components/location/LocationListItem.tsx | 8 +- .../components/settings/SettingsContainer.tsx | 2 +- .../components/settings/SettingsHeader.tsx | 12 +- .../settings/SettingsScreenContainer.tsx | 40 ++ .../interface/src/hooks/useCoreEvents.tsx | 46 -- packages/interface/src/index.ts | 3 +- packages/interface/src/screens/Debug.tsx | 21 +- packages/interface/src/screens/Explorer.tsx | 10 +- packages/interface/src/screens/Overview.tsx | 25 +- packages/interface/src/screens/Settings.tsx | 92 ---- .../settings/CurrentLibrarySettings.tsx | 42 ++ .../src/screens/settings/GeneralSettings.tsx | 40 -- .../src/screens/settings/LibrarySettings.tsx | 32 -- .../src/screens/settings/SecuritySettings.tsx | 23 - .../src/screens/settings/Settings.tsx | 83 ++++ .../{ => client}/AppearanceSettings.tsx | 4 +- .../settings/client/GeneralSettings.tsx | 35 ++ .../{ => library}/ContactsSettings.tsx | 4 +- .../settings/{ => library}/KeysSetting.tsx | 4 +- .../library/LibraryGeneralSettings.tsx | 91 ++++ .../settings/library/LocationSettings.tsx | 55 +++ .../settings/library/SecuritySettings.tsx | 14 + .../{ => library}/SharingSettings.tsx | 4 +- .../settings/{ => library}/SyncSettings.tsx | 4 +- .../settings/{ => library}/TagsSettings.tsx | 4 +- .../{ => node}/ExperimentalSettings.tsx | 12 +- .../settings/node/LibrariesSettings.tsx | 113 +++++ .../screens/settings/node/NodesSettings.tsx | 12 + .../src/screens/settings/node/P2PSettings.tsx | 40 ++ packages/ui/package.json | 2 +- packages/ui/src/Input.tsx | 2 +- pnpm-lock.yaml | Bin 621229 -> 625653 bytes 99 files changed, 2181 insertions(+), 1536 deletions(-) delete mode 100644 apps/server/k8s/infrastructure.yaml delete mode 100644 apps/server/k8s/sdserver.yaml create mode 100644 core/bindings/ConfigMetadata.ts create mode 100644 core/bindings/LibraryCommand.ts create mode 100644 core/bindings/LibraryConfig.ts create mode 100644 core/bindings/LibraryConfigWrapped.ts create mode 100644 core/bindings/LibraryQuery.ts create mode 100644 core/bindings/NodeConfig.ts create mode 100644 core/prisma/migrations/20220625180107_remove_library/migration.sql create mode 100644 core/src/library/library_config.rs create mode 100644 core/src/library/library_ctx.rs create mode 100644 core/src/library/library_manager.rs delete mode 100644 core/src/library/loader.rs create mode 100644 core/src/node/config.rs delete mode 100644 core/src/node/state.rs rename packages/{interface/src => client/src/context}/AppPropsContext.tsx (100%) create mode 100644 packages/client/src/context/index.ts delete mode 100644 packages/client/src/files/index.ts delete mode 100644 packages/client/src/files/query.ts delete mode 100644 packages/client/src/files/state.ts create mode 100644 packages/client/src/hooks/index.ts create mode 100644 packages/client/src/hooks/useCoreEvents.tsx create mode 100644 packages/client/src/stores/index.ts rename packages/{interface/src/hooks/useExplorerState.ts => client/src/stores/useExplorerStore.ts} (81%) rename packages/{interface/src/hooks/useInspectorState.tsx => client/src/stores/useInspectorStore.ts} (82%) create mode 100644 packages/client/src/stores/useLibraryStore.ts create mode 100644 packages/interface/src/components/layout/Card.tsx create mode 100644 packages/interface/src/components/settings/SettingsScreenContainer.tsx delete mode 100644 packages/interface/src/hooks/useCoreEvents.tsx delete mode 100644 packages/interface/src/screens/Settings.tsx create mode 100644 packages/interface/src/screens/settings/CurrentLibrarySettings.tsx delete mode 100644 packages/interface/src/screens/settings/GeneralSettings.tsx delete mode 100644 packages/interface/src/screens/settings/LibrarySettings.tsx delete mode 100644 packages/interface/src/screens/settings/SecuritySettings.tsx create mode 100644 packages/interface/src/screens/settings/Settings.tsx rename packages/interface/src/screens/settings/{ => client}/AppearanceSettings.tsx (58%) create mode 100644 packages/interface/src/screens/settings/client/GeneralSettings.tsx rename packages/interface/src/screens/settings/{ => library}/ContactsSettings.tsx (58%) rename packages/interface/src/screens/settings/{ => library}/KeysSetting.tsx (55%) create mode 100644 packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx create mode 100644 packages/interface/src/screens/settings/library/LocationSettings.tsx create mode 100644 packages/interface/src/screens/settings/library/SecuritySettings.tsx rename packages/interface/src/screens/settings/{ => library}/SharingSettings.tsx (58%) rename packages/interface/src/screens/settings/{ => library}/SyncSettings.tsx (56%) rename packages/interface/src/screens/settings/{ => library}/TagsSettings.tsx (55%) rename packages/interface/src/screens/settings/{ => node}/ExperimentalSettings.tsx (64%) create mode 100644 packages/interface/src/screens/settings/node/LibrariesSettings.tsx create mode 100644 packages/interface/src/screens/settings/node/NodesSettings.tsx create mode 100644 packages/interface/src/screens/settings/node/P2PSettings.tsx diff --git a/Cargo.lock b/Cargo.lock index ca71bf05e6c80fe1745988f1e5a2a2837b8930c0..3989e138c81bb696ef6e248feb128d429770c148 100644 GIT binary patch delta 4562 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 delta 4163 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 N4;ks~ZO!<+{{SfO>S+J~ diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index 3dcad488c..3c6210e46 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -1,16 +1,15 @@ use std::time::{Duration, Instant}; use dotenvy::dotenv; -use sdcore::{ClientCommand, ClientQuery, CoreController, CoreEvent, CoreResponse, Node}; -use tauri::api::path; -use tauri::Manager; +use sdcore::{ClientCommand, ClientQuery, CoreEvent, CoreResponse, Node, NodeController}; +use tauri::{api::path, Manager}; #[cfg(target_os = "macos")] mod macos; mod menu; #[tauri::command(async)] async fn client_query_transport( - core: tauri::State<'_, CoreController>, + core: tauri::State<'_, NodeController>, data: ClientQuery, ) -> Result { match core.query(data).await { @@ -24,7 +23,7 @@ async fn client_query_transport( #[tauri::command(async)] async fn client_command_transport( - core: tauri::State<'_, CoreController>, + core: tauri::State<'_, NodeController>, data: ClientCommand, ) -> Result { match core.command(data).await { @@ -48,17 +47,11 @@ async fn main() { dotenv().ok(); env_logger::init(); - let data_dir = path::data_dir().unwrap_or(std::path::PathBuf::from("./")); + let mut data_dir = path::data_dir().unwrap_or(std::path::PathBuf::from("./")); + data_dir = data_dir.join("spacedrive"); // create an instance of the core - let (mut node, mut event_receiver) = Node::new(data_dir).await; - // run startup tasks - node.initializer().await; - // extract the node controller - let controller = node.get_controller(); - // throw the node into a dedicated thread - tokio::spawn(async move { - node.start().await; - }); + let (controller, mut event_receiver, node) = Node::new(data_dir).await; + tokio::spawn(node.start()); // create tauri app tauri::Builder::default() // pass controller to the tauri state manager diff --git a/apps/server/k8s/infrastructure.yaml b/apps/server/k8s/infrastructure.yaml deleted file mode 100644 index a5e44b4ee..000000000 --- a/apps/server/k8s/infrastructure.yaml +++ /dev/null @@ -1,42 +0,0 @@ -# Infrastructure setups up the Kubernetes cluster for Spacedrive! -# -# To get the service account token use the following: -# ```bash -# TOKENNAME=`kubectl -n spacedrive get sa/spacedrive-ci -o jsonpath='{.secrets[0].name}'` -# kubectl -n spacedrive get secret $TOKENNAME -o jsonpath='{.data.token}' | base64 -d -# ``` - -apiVersion: v1 -kind: Namespace -metadata: - name: spacedrive ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: spacedrive-ci - namespace: spacedrive ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: spacedrive-ns-full - namespace: spacedrive -rules: - - apiGroups: ['apps'] - resources: ['deployments'] - verbs: ['get', 'patch'] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: spacedrive-ci-rb - namespace: spacedrive -subjects: - - kind: ServiceAccount - name: spacedrive-ci - namespace: spacedrive -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: spacedrive-ns-full diff --git a/apps/server/k8s/sdserver.yaml b/apps/server/k8s/sdserver.yaml deleted file mode 100644 index 00f02c1c1..000000000 --- a/apps/server/k8s/sdserver.yaml +++ /dev/null @@ -1,118 +0,0 @@ -# This will deploy the Spacedrive Server container to the `spacedrive`` namespace on Kubernetes. - -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: sdserver-ingress - namespace: spacedrive - labels: - app.kubernetes.io/name: sdserver - app.kubernetes.io/component: webserver - annotations: - traefik.ingress.kubernetes.io/router.tls.certresolver: le - traefik.ingress.kubernetes.io/router.middlewares: kube-system-antiseo@kubernetescrd -spec: - rules: - - host: spacedrive.otbeaumont.me - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: sdserver-service - port: - number: 8080 ---- -apiVersion: v1 -kind: Service -metadata: - name: sdserver-service - namespace: spacedrive - labels: - app.kubernetes.io/name: sdserver - app.kubernetes.io/component: webserver -spec: - ports: - - port: 8080 - targetPort: 8080 - protocol: TCP - selector: - app.kubernetes.io/name: sdserver - app.kubernetes.io/component: webserver ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: sdserver-pvc - namespace: spacedrive -spec: - accessModes: - - ReadWriteOnce - storageClassName: local-path - resources: - requests: - storage: 512M ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sdserver-deployment - namespace: spacedrive - labels: - app.kubernetes.io/name: sdserver - app.kubernetes.io/component: webserver -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: sdserver - app.kubernetes.io/component: webserver - template: - metadata: - labels: - app.kubernetes.io/name: sdserver - app.kubernetes.io/component: webserver - spec: - restartPolicy: Always - # refer to Dockerfile to find securityContext values - securityContext: - runAsUser: 101 - runAsGroup: 101 - fsGroup: 101 - containers: - - name: sdserver - image: ghcr.io/oscartbeaumont/spacedrive/server:staging - imagePullPolicy: Always - ports: - - containerPort: 8080 - volumeMounts: - - name: data-volume - mountPath: /data - securityContext: - allowPrivilegeEscalation: false - resources: - limits: - memory: 100Mi - cpu: 100m - requests: - memory: 5Mi - cpu: 10m - readinessProbe: - httpGet: - path: /health - port: 8080 - initialDelaySeconds: 10 - failureThreshold: 4 - periodSeconds: 5 - livenessProbe: - httpGet: - path: /health - port: 8080 - initialDelaySeconds: 20 - failureThreshold: 3 - periodSeconds: 10 - volumes: - - name: data-volume - persistentVolumeClaim: - claimName: sdserver-pvc diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index 7e9c4683e..5d7c85331 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -1,4 +1,4 @@ -use sdcore::{ClientCommand, ClientQuery, CoreController, CoreEvent, CoreResponse, Node}; +use sdcore::{ClientCommand, ClientQuery, CoreEvent, CoreResponse, Node, NodeController}; use std::{env, path::Path}; use actix::{ @@ -19,7 +19,7 @@ const DATA_DIR_ENV_VAR: &'static str = "DATA_DIR"; /// Define HTTP actor struct Socket { _event_receiver: web::Data>, - core: web::Data, + core: web::Data, } impl Actor for Socket { @@ -52,7 +52,15 @@ impl StreamHandler> for Socket { match msg { Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), Ok(ws::Message::Text(text)) => { - let msg: SocketMessage = serde_json::from_str(&text).unwrap(); + let msg = serde_json::from_str::(&text); + + let msg = match msg { + Ok(msg) => msg, + Err(err) => { + println!("Error parsing message: {}", err); + return; + }, + }; let core = self.core.clone(); @@ -133,7 +141,7 @@ async fn ws_handler( req: HttpRequest, stream: web::Payload, event_receiver: web::Data>, - controller: web::Data, + controller: web::Data, ) -> Result { let resp = ws::start( Socket { @@ -178,7 +186,7 @@ async fn main() -> std::io::Result<()> { async fn setup() -> ( web::Data>, - web::Data, + web::Data, ) { let data_dir_path = match env::var(DATA_DIR_ENV_VAR) { Ok(path) => Path::new(&path).to_path_buf(), @@ -196,15 +204,8 @@ async fn setup() -> ( }, }; - let (mut node, event_receiver) = Node::new(data_dir_path).await; - - node.initializer().await; - - let controller = node.get_controller(); - - tokio::spawn(async move { - node.start().await; - }); + let (controller, event_receiver, node) = Node::new(data_dir_path).await; + tokio::spawn(node.start()); (web::Data::new(event_receiver), web::Data::new(controller)) } diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 72560e3ff..07b125639 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -31,6 +31,16 @@ class Transport extends BaseTransport { }); } async query(query: ClientQuery) { + if (websocket.readyState == 0) { + let resolve: () => void; + const promise = new Promise((res) => { + resolve = () => res(undefined); + }); + // @ts-ignore + websocket.addEventListener('open', resolve); + await promise; + } + const id = randomId(); let resolve: (data: any) => void; diff --git a/core/Cargo.toml b/core/Cargo.toml index e748bfa2f..95e5f7546 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -24,10 +24,9 @@ ring = "0.17.0-alpha.10" int-enum = "0.4.0" # Project dependencies -ts-rs = { version = "6.1", features = ["chrono-impl"] } +ts-rs = { version = "6.1", features = ["chrono-impl", "uuid-impl"] } prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.5.0" } walkdir = "^2.3.2" -lazy_static = "1.4.0" uuid = "0.8" sysinfo = "0.23.9" thiserror = "1.0.30" diff --git a/core/bindings/ClientCommand.ts b/core/bindings/ClientCommand.ts index 2677dd55f..fd9b1f0a1 100644 --- a/core/bindings/ClientCommand.ts +++ b/core/bindings/ClientCommand.ts @@ -1,3 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { LibraryCommand } from "./LibraryCommand"; -export type ClientCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileDelete", params: { id: number, } } | { key: "LibDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { name: string, color: string, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocRescan", params: { id: number, } } | { key: "SysVolumeUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } }; \ No newline at end of file +export type ClientCommand = { key: "CreateLibrary", params: { name: string, } } | { key: "EditLibrary", params: { id: string, name: string | null, description: string | null, } } | { key: "DeleteLibrary", params: { id: string, } } | { key: "LibraryCommand", params: { library_id: string, command: LibraryCommand, } }; \ No newline at end of file diff --git a/core/bindings/ClientQuery.ts b/core/bindings/ClientQuery.ts index 9d4792a66..56e37988c 100644 --- a/core/bindings/ClientQuery.ts +++ b/core/bindings/ClientQuery.ts @@ -1,3 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { LibraryQuery } from "./LibraryQuery"; -export type ClientQuery = { key: "NodeGetState" } | { key: "SysGetVolumes" } | { key: "LibGetTags" } | { key: "JobGetRunning" } | { key: "JobGetHistory" } | { key: "SysGetLocations" } | { key: "SysGetLocation", params: { id: number, } } | { key: "LibGetExplorerDir", params: { location_id: number, path: string, limit: number, } } | { key: "GetLibraryStatistics" } | { key: "GetNodes" }; \ No newline at end of file +export type ClientQuery = { key: "NodeGetLibraries" } | { key: "NodeGetState" } | { key: "SysGetVolumes" } | { key: "JobGetRunning" } | { key: "GetNodes" } | { key: "LibraryQuery", params: { library_id: string, query: LibraryQuery, } }; \ No newline at end of file diff --git a/core/bindings/ConfigMetadata.ts b/core/bindings/ConfigMetadata.ts new file mode 100644 index 000000000..d12aaa575 --- /dev/null +++ b/core/bindings/ConfigMetadata.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface ConfigMetadata { version: string | null, } \ No newline at end of file diff --git a/core/bindings/CoreResponse.ts b/core/bindings/CoreResponse.ts index 94dc0568c..cabf79dfa 100644 --- a/core/bindings/CoreResponse.ts +++ b/core/bindings/CoreResponse.ts @@ -1,9 +1,10 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DirectoryWithContents } from "./DirectoryWithContents"; import type { JobReport } from "./JobReport"; +import type { LibraryConfigWrapped } from "./LibraryConfigWrapped"; import type { LocationResource } from "./LocationResource"; import type { NodeState } from "./NodeState"; import type { Statistics } from "./Statistics"; import type { Volume } from "./Volume"; -export type CoreResponse = { key: "Success", data: null } | { key: "SysGetVolumes", data: Array } | { key: "SysGetLocation", data: LocationResource } | { key: "SysGetLocations", data: Array } | { key: "LibGetExplorerDir", data: DirectoryWithContents } | { key: "NodeGetState", data: NodeState } | { key: "LocCreate", data: LocationResource } | { key: "JobGetRunning", data: Array } | { key: "JobGetHistory", data: Array } | { key: "GetLibraryStatistics", data: Statistics }; \ No newline at end of file +export type CoreResponse = { key: "Success", data: null } | { key: "Error", data: string } | { key: "NodeGetLibraries", data: Array } | { key: "SysGetVolumes", data: Array } | { key: "SysGetLocation", data: LocationResource } | { key: "SysGetLocations", data: Array } | { key: "LibGetExplorerDir", data: DirectoryWithContents } | { key: "NodeGetState", data: NodeState } | { key: "LocCreate", data: LocationResource } | { key: "JobGetRunning", data: Array } | { key: "JobGetHistory", data: Array } | { key: "GetLibraryStatistics", data: Statistics }; \ No newline at end of file diff --git a/core/bindings/LibraryCommand.ts b/core/bindings/LibraryCommand.ts new file mode 100644 index 000000000..713fc8989 --- /dev/null +++ b/core/bindings/LibraryCommand.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LibraryCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { name: string, color: string, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocRescan", params: { id: number, } } | { key: "SysVolumeUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } }; \ No newline at end of file diff --git a/core/bindings/LibraryConfig.ts b/core/bindings/LibraryConfig.ts new file mode 100644 index 000000000..8a371014b --- /dev/null +++ b/core/bindings/LibraryConfig.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface LibraryConfig { version: string | null, name: string, description: string, } \ No newline at end of file diff --git a/core/bindings/LibraryConfigWrapped.ts b/core/bindings/LibraryConfigWrapped.ts new file mode 100644 index 000000000..ee5b5ccfe --- /dev/null +++ b/core/bindings/LibraryConfigWrapped.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { LibraryConfig } from "./LibraryConfig"; + +export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig, } \ No newline at end of file diff --git a/core/bindings/LibraryQuery.ts b/core/bindings/LibraryQuery.ts new file mode 100644 index 000000000..2aa14279c --- /dev/null +++ b/core/bindings/LibraryQuery.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LibraryQuery = { key: "LibGetTags" } | { key: "JobGetHistory" } | { key: "SysGetLocations" } | { key: "SysGetLocation", params: { id: number, } } | { key: "LibGetExplorerDir", params: { location_id: number, path: string, limit: number, } } | { key: "GetLibraryStatistics" }; \ No newline at end of file diff --git a/core/bindings/NodeConfig.ts b/core/bindings/NodeConfig.ts new file mode 100644 index 000000000..512f0202c --- /dev/null +++ b/core/bindings/NodeConfig.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null, } \ No newline at end of file diff --git a/core/bindings/NodeState.ts b/core/bindings/NodeState.ts index 6fc2d5c22..978fb3103 100644 --- a/core/bindings/NodeState.ts +++ b/core/bindings/NodeState.ts @@ -1,4 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { LibraryState } from "./LibraryState"; -export interface NodeState { node_pub_id: string, node_id: number, node_name: string, data_path: string, tcp_port: number, libraries: Array, current_library_uuid: string, } \ No newline at end of file +export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string, } \ No newline at end of file diff --git a/core/index.ts b/core/index.ts index 85eee6629..60cc1bc54 100644 --- a/core/index.ts +++ b/core/index.ts @@ -2,6 +2,7 @@ export * from './bindings/Client'; export * from './bindings/ClientCommand'; export * from './bindings/ClientQuery'; export * from './bindings/ClientState'; +export * from './bindings/ConfigMetadata'; export * from './bindings/CoreEvent'; export * from './bindings/CoreResource'; export * from './bindings/CoreResponse'; @@ -12,9 +13,14 @@ export * from './bindings/FileKind'; export * from './bindings/FilePath'; export * from './bindings/JobReport'; export * from './bindings/JobStatus'; +export * from './bindings/LibraryCommand'; +export * from './bindings/LibraryConfig'; +export * from './bindings/LibraryConfigWrapped'; export * from './bindings/LibraryNode'; +export * from './bindings/LibraryQuery'; export * from './bindings/LibraryState'; export * from './bindings/LocationResource'; +export * from './bindings/NodeConfig'; export * from './bindings/NodeState'; export * from './bindings/Platform'; export * from './bindings/Statistics'; diff --git a/core/prisma/migrations/20220625180107_remove_library/migration.sql b/core/prisma/migrations/20220625180107_remove_library/migration.sql new file mode 100644 index 000000000..63e4f056f --- /dev/null +++ b/core/prisma/migrations/20220625180107_remove_library/migration.sql @@ -0,0 +1,29 @@ +/* + Warnings: + + - You are about to drop the `libraries` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `library_statistics` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropTable +PRAGMA foreign_keys=off; +DROP TABLE "libraries"; +PRAGMA foreign_keys=on; + +-- DropTable +PRAGMA foreign_keys=off; +DROP TABLE "library_statistics"; +PRAGMA foreign_keys=on; + +-- CreateTable +CREATE TABLE "statistics" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "date_captured" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "total_file_count" INTEGER NOT NULL DEFAULT 0, + "library_db_size" TEXT NOT NULL DEFAULT '0', + "total_bytes_used" TEXT NOT NULL DEFAULT '0', + "total_bytes_capacity" TEXT NOT NULL DEFAULT '0', + "total_unique_bytes" TEXT NOT NULL DEFAULT '0', + "total_bytes_free" TEXT NOT NULL DEFAULT '0', + "preview_media_bytes" TEXT NOT NULL DEFAULT '0' +); diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index e8f911004..130151f62 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -35,21 +35,9 @@ model SyncEvent { @@map("sync_events") } -model Library { - id Int @id @default(autoincrement()) - pub_id String @unique - name String - is_primary Boolean @default(true) - date_created DateTime @default(now()) - timezone String? - - @@map("libraries") -} - -model LibraryStatistics { +model Statistics { id Int @id @default(autoincrement()) date_captured DateTime @default(now()) - library_id Int @unique total_file_count Int @default(0) library_db_size String @default("0") total_bytes_used String @default("0") @@ -58,7 +46,7 @@ model LibraryStatistics { total_bytes_free String @default("0") preview_media_bytes String @default("0") - @@map("library_statistics") + @@map("statistics") } model Node { diff --git a/core/src/encode/thumb.rs b/core/src/encode/thumb.rs index bb148d46b..f4665551e 100644 --- a/core/src/encode/thumb.rs +++ b/core/src/encode/thumb.rs @@ -1,9 +1,8 @@ -use crate::job::JobReportUpdate; -use crate::node::get_nodestate; +use crate::job::{JobReportUpdate, JobResult}; +use crate::library::LibraryContext; use crate::{ job::{Job, WorkerContext}, prisma::file_path, - CoreContext, }; use crate::{sys, CoreEvent}; use futures::executor::block_on; @@ -29,11 +28,18 @@ impl Job for ThumbnailJob { fn name(&self) -> &'static str { "thumbnailer" } - async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { - let config = get_nodestate(); - let core_ctx = ctx.core_ctx.clone(); + async fn run(&self, ctx: WorkerContext) -> JobResult { + let library_ctx = ctx.library_ctx(); + let thumbnail_dir = Path::new(&library_ctx.config().data_directory()) + .join(THUMBNAIL_CACHE_DIR_NAME) + .join(format!("{}", self.location_id)); - let location = sys::get_location(&core_ctx, self.location_id).await?; + let location = sys::get_location(&library_ctx, self.location_id).await?; + + info!( + "Searching for images in location {} at path {}", + location.id, self.path + ); info!( "Searching for images in location {} at path {}", @@ -41,17 +47,12 @@ impl Job for ThumbnailJob { ); // create all necessary directories if they don't exist - fs::create_dir_all( - Path::new(&config.data_path) - .join(THUMBNAIL_CACHE_DIR_NAME) - .join(format!("{}", self.location_id)), - )?; + fs::create_dir_all(&thumbnail_dir)?; let root_path = location.path.unwrap(); // query database for all files in this location that need thumbnails - let image_files = get_images(&core_ctx, self.location_id, &self.path).await?; + let image_files = get_images(&library_ctx, self.location_id, &self.path).await?; info!("Found {:?} files", image_files.len()); - let is_background = self.background.clone(); tokio::task::spawn_blocking(move || { @@ -89,9 +90,7 @@ impl Job for ThumbnailJob { }; // Define and write the WebP-encoded file to a given path - let output_path = Path::new(&config.data_path) - .join(THUMBNAIL_CACHE_DIR_NAME) - .join(format!("{}", location.id)) + let output_path = Path::new(&thumbnail_dir) .join(&cas_id) .with_extension("webp"); @@ -107,7 +106,7 @@ impl Job for ThumbnailJob { ctx.progress(vec![JobReportUpdate::CompletedTaskCount(i + 1)]); if !is_background { - block_on(ctx.core_ctx.emit(CoreEvent::NewThumbnail { cas_id })); + block_on(ctx.library_ctx().emit(CoreEvent::NewThumbnail { cas_id })); }; } else { info!("Thumb exists, skipping... {}", output_path.display()); @@ -146,7 +145,7 @@ pub fn generate_thumbnail( } pub async fn get_images( - ctx: &CoreContext, + ctx: &LibraryContext, location_id: i32, path: &str, ) -> Result, std::io::Error> { @@ -166,7 +165,7 @@ pub async fn get_images( } let image_files = ctx - .database + .db .file_path() .find_many(params) .with(file_path::file::fetch()) diff --git a/core/src/file/cas/identifier.rs b/core/src/file/cas/identifier.rs index 94e5c5aaf..251ae4120 100644 --- a/core/src/file/cas/identifier.rs +++ b/core/src/file/cas/identifier.rs @@ -2,10 +2,10 @@ use super::checksum::generate_cas_id; use crate::{ file::FileError, job::JobReportUpdate, - job::{Job, WorkerContext}, + job::{Job, JobResult, WorkerContext}, + library::LibraryContext, prisma::{file, file_path}, sys::get_location, - CoreContext, }; use chrono::{DateTime, FixedOffset}; use futures::executor::block_on; @@ -33,13 +33,14 @@ impl Job for FileIdentifierJob { fn name(&self) -> &'static str { "file_identifier" } - async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { + + async fn run(&self, ctx: WorkerContext) -> JobResult { info!("Identifying orphan file paths..."); - let location = get_location(&ctx.core_ctx, self.location_id).await?; + let location = get_location(&ctx.library_ctx(), self.location_id).await?; let location_path = location.path.unwrap_or("".to_string()); - let total_count = count_orphan_file_paths(&ctx.core_ctx, location.id.into()).await?; + let total_count = count_orphan_file_paths(&ctx.library_ctx(), location.id.into()).await?; info!("Found {} orphan file paths", total_count); let task_count = (total_count as f64 / CHUNK_SIZE as f64).ceil() as usize; @@ -48,9 +49,9 @@ impl Job for FileIdentifierJob { // update job with total task count based on orphan file_paths count ctx.progress(vec![JobReportUpdate::TaskCount(task_count)]); - let db = ctx.core_ctx.database.clone(); // dedicated tokio thread for task let _ctx = tokio::task::spawn_blocking(move || { + let db = ctx.library_ctx().db; let mut completed: usize = 0; let mut cursor: i32 = 1; // loop until task count is complete @@ -60,7 +61,7 @@ impl Job for FileIdentifierJob { let mut cas_lookup: HashMap = HashMap::new(); // get chunk of orphans to process - let file_paths = match block_on(get_orphan_file_paths(&ctx.core_ctx, cursor)) { + let file_paths = match block_on(get_orphan_file_paths(&ctx.library_ctx(), cursor)) { Ok(file_paths) => file_paths, Err(e) => { info!("Error getting orphan file paths: {}", e); @@ -192,11 +193,10 @@ struct CountRes { } pub async fn count_orphan_file_paths( - ctx: &CoreContext, + ctx: &LibraryContext, location_id: i64, ) -> Result { - let db = &ctx.database; - let files_count = db + let files_count = ctx.db ._query_raw::(raw!( "SELECT COUNT(*) AS count FROM file_paths WHERE file_id IS NULL AND is_dir IS FALSE AND location_id = {}", PrismaValue::Int(location_id) @@ -206,10 +206,10 @@ pub async fn count_orphan_file_paths( } pub async fn get_orphan_file_paths( - ctx: &CoreContext, + ctx: &LibraryContext, cursor: i32, ) -> Result, FileError> { - let db = &ctx.database; + let db = &ctx.db; info!( "discovering {} orphan file paths at cursor: {:?}", CHUNK_SIZE, cursor @@ -225,6 +225,7 @@ pub async fn get_orphan_file_paths( .take(CHUNK_SIZE as i64) .exec() .await?; + Ok(files) } diff --git a/core/src/file/explorer/open.rs b/core/src/file/explorer/open.rs index bedfba5a0..8afe1c9d8 100644 --- a/core/src/file/explorer/open.rs +++ b/core/src/file/explorer/open.rs @@ -1,25 +1,22 @@ use crate::{ encode::THUMBNAIL_CACHE_DIR_NAME, file::{DirectoryWithContents, FileError, FilePath}, - node::get_nodestate, + library::LibraryContext, prisma::file_path, sys::get_location, - CoreContext, }; use std::path::Path; pub async fn open_dir( - ctx: &CoreContext, + ctx: &LibraryContext, location_id: &i32, path: &str, ) -> Result { - let db = &ctx.database; - let config = get_nodestate(); - // get location let location = get_location(ctx, location_id.clone()).await?; - let directory = db + let directory = ctx + .db .file_path() .find_first(vec![ file_path::location_id::equals(Some(location.id)), @@ -32,7 +29,8 @@ pub async fn open_dir( println!("DIRECTORY: {:?}", directory); - let mut file_paths: Vec = db + let mut file_paths: Vec = ctx + .db .file_path() .find_many(vec![ file_path::location_id::equals(Some(location.id)), @@ -47,7 +45,7 @@ pub async fn open_dir( for file_path in &mut file_paths { if let Some(file) = &mut file_path.file { - let thumb_path = Path::new(&config.data_path) + let thumb_path = Path::new(&ctx.config().data_directory()) .join(THUMBNAIL_CACHE_DIR_NAME) .join(format!("{}", location.id)) .join(file.cas_id.clone()) diff --git a/core/src/file/indexer/mod.rs b/core/src/file/indexer/mod.rs index 4415b252c..c5e54d036 100644 --- a/core/src/file/indexer/mod.rs +++ b/core/src/file/indexer/mod.rs @@ -1,4 +1,4 @@ -use crate::job::{Job, JobReportUpdate, WorkerContext}; +use crate::job::{Job, JobReportUpdate, JobResult, WorkerContext}; use self::scan::ScanProgress; mod scan; @@ -17,9 +17,8 @@ impl Job for IndexerJob { fn name(&self) -> &'static str { "indexer" } - async fn run(&self, ctx: WorkerContext) -> Result<(), Box> { - let core_ctx = ctx.core_ctx.clone(); - scan_path(&core_ctx, self.path.as_str(), move |p| { + async fn run(&self, ctx: WorkerContext) -> JobResult { + scan_path(&ctx.library_ctx(), self.path.as_str(), move |p| { ctx.progress( p.iter() .map(|p| match p.clone() { diff --git a/core/src/file/indexer/scan.rs b/core/src/file/indexer/scan.rs index 19c3ee53d..0651b7b40 100644 --- a/core/src/file/indexer/scan.rs +++ b/core/src/file/indexer/scan.rs @@ -1,6 +1,7 @@ +use crate::job::JobResult; +use crate::library::LibraryContext; use crate::sys::{create_location, LocationResource}; -use crate::CoreContext; -use chrono::{DateTime, FixedOffset, Utc}; +use chrono::{DateTime, Utc}; use log::{error, info}; use prisma_client_rust::prisma_models::PrismaValue; use prisma_client_rust::raw; @@ -21,11 +22,10 @@ static BATCH_SIZE: usize = 100; // creates a vector of valid path buffers from a directory pub async fn scan_path( - ctx: &CoreContext, + ctx: &LibraryContext, path: &str, on_progress: impl Fn(Vec) + Send + Sync + 'static, -) -> Result<(), Box> { - let db = &ctx.database; +) -> JobResult { let path = path.to_string(); let location = create_location(&ctx, &path).await?; @@ -36,7 +36,8 @@ pub async fn scan_path( id: Option, } // grab the next id so we can increment in memory for batch inserting - let first_file_id = match db + let first_file_id = match ctx + .db ._query_raw::(raw!("SELECT MAX(id) id FROM file_paths")) .await { @@ -162,7 +163,7 @@ pub async fn scan_path( files ); - let count = db._execute_raw(raw).await; + let count = ctx.db._execute_raw(raw).await; info!("Inserted {:?} records", count); } diff --git a/core/src/file/mod.rs b/core/src/file/mod.rs index bc632ecec..c8955e7f1 100644 --- a/core/src/file/mod.rs +++ b/core/src/file/mod.rs @@ -1,12 +1,14 @@ +use chrono::{DateTime, Utc}; use int_enum::IntEnum; use serde::{Deserialize, Serialize}; use thiserror::Error; use ts_rs::TS; use crate::{ + library::LibraryContext, prisma::{self, file, file_path}, sys::SysError, - ClientQuery, CoreContext, CoreError, CoreEvent, CoreResponse, + ClientQuery, CoreError, CoreEvent, CoreResponse, LibraryQuery, }; pub mod cas; pub mod explorer; @@ -32,9 +34,9 @@ pub struct File { pub ipfs_id: Option, pub note: Option, - pub date_created: chrono::DateTime, - pub date_modified: chrono::DateTime, - pub date_indexed: chrono::DateTime, + pub date_created: DateTime, + pub date_modified: DateTime, + pub date_indexed: DateTime, pub paths: Vec, // pub media_data: Option, @@ -55,9 +57,9 @@ pub struct FilePath { pub file_id: Option, pub parent_id: Option, - pub date_created: chrono::DateTime, - pub date_modified: chrono::DateTime, - pub date_indexed: chrono::DateTime, + pub date_created: DateTime, + pub date_modified: DateTime, + pub date_indexed: DateTime, pub file: Option, } @@ -141,12 +143,12 @@ pub enum FileError { } pub async fn set_note( - ctx: CoreContext, + ctx: LibraryContext, id: i32, note: Option, ) -> Result { - let response = ctx - .database + let _response = ctx + .db .file() .find_unique(file::id::equals(id)) .update(vec![file::note::set(note.clone())]) @@ -154,10 +156,13 @@ pub async fn set_note( .await .unwrap(); - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibGetExplorerDir { - limit: 0, - path: "".to_string(), - location_id: 0, + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::LibGetExplorerDir { + limit: 0, + path: "".to_string(), + location_id: 0, + }, })) .await; diff --git a/core/src/job/jobs.rs b/core/src/job/jobs.rs index efacd6cb8..cb4f42cab 100644 --- a/core/src/job/jobs.rs +++ b/core/src/job/jobs.rs @@ -3,48 +3,69 @@ use super::{ JobError, }; use crate::{ - node::get_nodestate, + library::LibraryContext, prisma::{job, node}, - CoreContext, }; use int_enum::IntEnum; use log::info; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, VecDeque}, + error::Error, fmt::Debug, sync::Arc, }; -use tokio::sync::Mutex; +use tokio::sync::{mpsc, Mutex, RwLock}; use ts_rs::TS; // db is single threaded, nerd const MAX_WORKERS: usize = 1; +pub type JobResult = Result<(), Box>; + #[async_trait::async_trait] pub trait Job: Send + Sync + Debug { - async fn run(&self, ctx: WorkerContext) -> Result<(), Box>; + async fn run(&self, ctx: WorkerContext) -> JobResult; fn name(&self) -> &'static str; } -// jobs struct is maintained by the core -pub struct Jobs { - job_queue: VecDeque>, - // workers are spawned when jobs are picked off the queue - running_workers: HashMap>>, +pub enum JobManagerEvent { + IngestJob(LibraryContext, Box), } -impl Jobs { - pub fn new() -> Self { - Self { - job_queue: VecDeque::new(), - running_workers: HashMap::new(), - } +// jobs struct is maintained by the core +pub struct JobManager { + job_queue: RwLock>>, + // workers are spawned when jobs are picked off the queue + running_workers: RwLock>>>, + internal_sender: mpsc::UnboundedSender, +} + +impl JobManager { + pub fn new() -> Arc { + let (internal_sender, mut internal_reciever) = mpsc::unbounded_channel(); + let this = Arc::new(Self { + job_queue: RwLock::new(VecDeque::new()), + running_workers: RwLock::new(HashMap::new()), + internal_sender, + }); + + let this2 = this.clone(); + tokio::spawn(async move { + while let Some(event) = internal_reciever.recv().await { + match event { + JobManagerEvent::IngestJob(ctx, job) => this2.clone().ingest(&ctx, job).await, + } + } + }); + + this } - pub async fn ingest(&mut self, ctx: &CoreContext, job: Box) { + pub async fn ingest(self: Arc, ctx: &LibraryContext, job: Box) { // create worker to process job - if self.running_workers.len() < MAX_WORKERS { + let mut running_workers = self.running_workers.write().await; + if running_workers.len() < MAX_WORKERS { info!("Running job: {:?}", job.name()); let worker = Worker::new(job); @@ -52,51 +73,57 @@ impl Jobs { let wrapped_worker = Arc::new(Mutex::new(worker)); - Worker::spawn(wrapped_worker.clone(), ctx).await; + Worker::spawn(self.clone(), wrapped_worker.clone(), ctx).await; - self.running_workers.insert(id, wrapped_worker); + running_workers.insert(id, wrapped_worker); } else { - self.job_queue.push_back(job); + self.job_queue.write().await.push_back(job); } } - pub fn ingest_queue(&mut self, _ctx: &CoreContext, job: Box) { - self.job_queue.push_back(job); + pub async fn ingest_queue(&self, _ctx: &LibraryContext, job: Box) { + self.job_queue.write().await.push_back(job); } - pub async fn complete(&mut self, ctx: &CoreContext, job_id: String) { + + pub async fn complete(self: Arc, ctx: &LibraryContext, job_id: String) { // remove worker from running workers - self.running_workers.remove(&job_id); + self.running_workers.write().await.remove(&job_id); // continue queue - let job = self.job_queue.pop_front(); + let job = self.job_queue.write().await.pop_front(); if let Some(job) = job { - self.ingest(ctx, job).await; + // We can't directly execute `self.ingest` here because it would cause an async cycle. + self.internal_sender + .send(JobManagerEvent::IngestJob(ctx.clone(), job)) + .unwrap_or_else(|_| { + println!("Failed to ingest job!"); + }); } } pub async fn get_running(&self) -> Vec { let mut ret = vec![]; - for worker in self.running_workers.values() { + for worker in self.running_workers.read().await.values() { let worker = worker.lock().await; ret.push(worker.job_report.clone()); } ret } - pub async fn queue_pending_job(ctx: &CoreContext) -> Result<(), JobError> { - let db = &ctx.database; + // pub async fn queue_pending_job(ctx: &LibraryContext) -> Result<(), JobError> { + // let db = &ctx.db; - let next_job = db - .job() - .find_first(vec![job::status::equals(JobStatus::Queued.int_value())]) - .exec() - .await?; + // let _next_job = db + // .job() + // .find_first(vec![job::status::equals(JobStatus::Queued.int_value())]) + // .exec() + // .await?; - Ok(()) - } + // Ok(()) + // } - pub async fn get_history(ctx: &CoreContext) -> Result, JobError> { - let db = &ctx.database; + pub async fn get_history(ctx: &LibraryContext) -> Result, JobError> { + let db = &ctx.db; let jobs = db .job() .find_many(vec![job::status::not(JobStatus::Running.int_value())]) @@ -172,30 +199,29 @@ impl JobReport { seconds_elapsed: 0, } } - pub async fn create(&self, ctx: &CoreContext) -> Result<(), JobError> { - let config = get_nodestate(); + pub async fn create(&self, ctx: &LibraryContext) -> Result<(), JobError> { let mut params = Vec::new(); if let Some(_) = &self.data { params.push(job::data::set(self.data.clone())) } - ctx.database + ctx.db .job() .create( job::id::set(self.id.clone()), job::name::set(self.name.clone()), job::action::set(1), - job::nodes::link(node::id::equals(config.node_id)), + job::nodes::link(node::id::equals(ctx.node_local_id)), params, ) .exec() .await?; Ok(()) } - pub async fn update(&self, ctx: &CoreContext) -> Result<(), JobError> { - ctx.database + pub async fn update(&self, ctx: &LibraryContext) -> Result<(), JobError> { + ctx.db .job() .find_unique(job::id::equals(self.id.clone())) .update(vec![ diff --git a/core/src/job/worker.rs b/core/src/job/worker.rs index 6022e603e..29c0ec17e 100644 --- a/core/src/job/worker.rs +++ b/core/src/job/worker.rs @@ -1,8 +1,8 @@ use super::{ jobs::{JobReport, JobReportUpdate, JobStatus}, - Job, + Job, JobManager, }; -use crate::{ClientQuery, CoreContext, CoreEvent, InternalEvent}; +use crate::{library::LibraryContext, ClientQuery, CoreEvent, LibraryQuery}; use std::{sync::Arc, time::Duration}; use tokio::{ sync::{ @@ -26,8 +26,8 @@ enum WorkerState { #[derive(Clone)] pub struct WorkerContext { pub uuid: String, - pub core_ctx: CoreContext, - pub sender: UnboundedSender, + library_ctx: LibraryContext, + sender: UnboundedSender, } impl WorkerContext { @@ -36,9 +36,13 @@ impl WorkerContext { .send(WorkerEvent::Progressed(updates)) .unwrap_or(()); } + + pub fn library_ctx(&self) -> LibraryContext { + self.library_ctx.clone() + } + // save the job data to // pub fn save_data () { - // } } @@ -63,7 +67,11 @@ impl Worker { } } // spawns a thread and extracts channel sender to communicate with it - pub async fn spawn(worker: Arc>, ctx: &CoreContext) { + pub async fn spawn( + job_manager: Arc, + worker: Arc>, + ctx: &LibraryContext, + ) { // we capture the worker receiver channel so state can be updated from inside the worker let mut worker_mut = worker.lock().await; // extract owned job and receiver from Self @@ -76,10 +84,11 @@ impl Worker { WorkerState::Running => unreachable!(), }; let worker_sender = worker_mut.worker_sender.clone(); - let core_ctx = ctx.clone(); worker_mut.job_report.status = JobStatus::Running; + let ctx = ctx.clone(); + worker_mut.job_report.create(&ctx).await.unwrap_or(()); // spawn task to handle receiving events from the worker @@ -94,7 +103,7 @@ impl Worker { tokio::spawn(async move { let worker_ctx = WorkerContext { uuid, - core_ctx, + library_ctx: ctx.clone(), sender: worker_sender, }; let job_start = Instant::now(); @@ -113,20 +122,17 @@ impl Worker { } }); - let result = job.run(worker_ctx.clone()).await; - - if let Err(e) = result { - println!("job failed {:?}", e); - worker_ctx.sender.send(WorkerEvent::Failed).unwrap_or(()); - } else { - // handle completion - worker_ctx.sender.send(WorkerEvent::Completed).unwrap_or(()); + match job.run(worker_ctx.clone()).await { + Ok(_) => { + worker_ctx.sender.send(WorkerEvent::Completed).unwrap_or(()); + } + Err(err) => { + println!("job '{}' failed with error: {}", worker_ctx.uuid, err); + worker_ctx.sender.send(WorkerEvent::Failed).unwrap_or(()); + } } - worker_ctx - .core_ctx - .internal_sender - .send(InternalEvent::JobComplete(worker_ctx.uuid.clone())) - .unwrap_or(()); + + job_manager.complete(&ctx, worker_ctx.uuid).await; }); } @@ -137,7 +143,7 @@ impl Worker { async fn track_progress( worker: Arc>, mut channel: UnboundedReceiver, - ctx: CoreContext, + ctx: LibraryContext, ) { while let Some(command) = channel.recv().await { let mut worker = worker.lock().await; @@ -176,16 +182,23 @@ impl Worker { ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::JobGetRunning)) .await; - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::JobGetHistory)) - .await; + + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::JobGetHistory, + })) + .await; break; } WorkerEvent::Failed => { worker.job_report.status = JobStatus::Failed; worker.job_report.update(&ctx).await.unwrap_or(()); - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::JobGetHistory)) - .await; + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::JobGetHistory, + })) + .await; break; } } diff --git a/core/src/lib.rs b/core/src/lib.rs index bb096a666..21153848b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,11 +1,13 @@ -use crate::{ - file::cas::FileIdentifierJob, library::get_library_path, node::NodeState, - prisma::file as prisma_file, prisma::location, util::db::create_connection, -}; -use job::{Job, JobReport, Jobs}; -use prisma::PrismaClient; +use crate::{file::cas::FileIdentifierJob, prisma::file as prisma_file, prisma::location}; +use job::{JobManager, JobReport}; +use library::{LibraryConfig, LibraryConfigWrapped, LibraryManager}; +use node::{NodeConfig, NodeConfigManager}; use serde::{Deserialize, Serialize}; -use std::{fs, sync::Arc}; +use std::{ + fs, + path::{Path, PathBuf}, + sync::Arc, +}; use thiserror::Error; use tokio::sync::{ mpsc::{self, unbounded_channel, UnboundedReceiver, UnboundedSender}, @@ -32,12 +34,12 @@ pub struct ReturnableMessage> { } // core controller is passed to the client to communicate with the core which runs in a dedicated thread -pub struct CoreController { +pub struct NodeController { query_sender: UnboundedSender>, command_sender: UnboundedSender>, } -impl CoreController { +impl NodeController { pub async fn query(&self, query: ClientQuery) -> Result { // a one time use channel to send and await a response let (sender, recv) = oneshot::channel(); @@ -64,35 +66,14 @@ impl CoreController { } } -#[derive(Debug)] -pub enum InternalEvent { - JobIngest(Box), - JobQueue(Box), - JobComplete(String), -} - #[derive(Clone)] -pub struct CoreContext { - pub database: Arc, +pub struct NodeContext { pub event_sender: mpsc::Sender, - pub internal_sender: UnboundedSender, + pub config: Arc, + pub jobs: Arc, } -impl CoreContext { - pub fn spawn_job(&self, job: Box) { - self.internal_sender - .send(InternalEvent::JobIngest(job)) - .unwrap_or_else(|e| { - println!("Failed to spawn job. {:?}", e); - }); - } - pub fn queue_job(&self, job: Box) { - self.internal_sender - .send(InternalEvent::JobQueue(job)) - .unwrap_or_else(|e| { - println!("Failed to queue job. {:?}", e); - }); - } +impl NodeContext { pub async fn emit(&self, event: CoreEvent) { self.event_sender.send(event).await.unwrap_or_else(|e| { println!("Failed to emit event. {:?}", e); @@ -101,11 +82,9 @@ impl CoreContext { } pub struct Node { - state: NodeState, - jobs: job::Jobs, - database: Arc, - // filetype_registry: library::TypeRegistry, - // extension_registry: library::ExtensionRegistry, + config: Arc, + library_manager: Arc, + jobs: Arc, // global messaging channels query_channel: ( @@ -117,73 +96,52 @@ pub struct Node { UnboundedReceiver>, ), event_sender: mpsc::Sender, - - // a channel for child threads to send events back to the core - internal_channel: ( - UnboundedSender, - UnboundedReceiver, - ), } impl Node { // create new instance of node, run startup tasks - pub async fn new(mut data_dir: std::path::PathBuf) -> (Node, mpsc::Receiver) { - let (event_sender, event_recv) = mpsc::channel(100); - - data_dir = data_dir.join("spacedrive"); - let data_dir = data_dir.to_str().unwrap(); - // create data directory if it doesn't exist + pub async fn new(data_dir: PathBuf) -> (NodeController, mpsc::Receiver, Node) { fs::create_dir_all(&data_dir).unwrap(); - // prepare basic client state - let mut state = NodeState::new(data_dir, "diamond-mastering-space-dragon").unwrap(); - // load from disk - state - .read_disk() - .unwrap_or(println!("Error: No node state found, creating new one...")); - state.save(); - - println!("Node State: {:?}", state); - - // connect to default library - let database = Arc::new( - create_connection(&get_library_path(&data_dir)) - .await - .unwrap(), - ); - - let internal_channel = unbounded_channel::(); - - let node = Node { - state, - query_channel: unbounded_channel(), - command_channel: unbounded_channel(), - jobs: Jobs::new(), - event_sender, - database, - internal_channel, + let (event_sender, event_recv) = mpsc::channel(100); + let config = NodeConfigManager::new(data_dir.clone()).await.unwrap(); + let jobs = JobManager::new(); + let node_ctx = NodeContext { + event_sender: event_sender.clone(), + config: config.clone(), + jobs: jobs.clone(), }; - (node, event_recv) + let node = Node { + config, + library_manager: LibraryManager::new(Path::new(&data_dir).join("libraries"), node_ctx) + .await + .unwrap(), + query_channel: unbounded_channel(), + command_channel: unbounded_channel(), + jobs, + event_sender, + }; + + ( + NodeController { + query_sender: node.query_channel.0.clone(), + command_sender: node.command_channel.0.clone(), + }, + event_recv, + node, + ) } - pub fn get_context(&self) -> CoreContext { - CoreContext { - database: self.database.clone(), + pub fn get_context(&self) -> NodeContext { + NodeContext { event_sender: self.event_sender.clone(), - internal_sender: self.internal_channel.0.clone(), + config: self.config.clone(), + jobs: self.jobs.clone(), } } - pub fn get_controller(&self) -> CoreController { - CoreController { - query_sender: self.query_channel.0.clone(), - command_sender: self.command_channel.0.clone(), - } - } - - pub async fn start(&mut self) { - let ctx = self.get_context(); + pub async fn start(mut self) { loop { // listen on global messaging channels for incoming messages tokio::select! { @@ -195,174 +153,200 @@ impl Node { let res = self.exec_command(msg.data).await; msg.return_sender.send(res).unwrap_or(()); } - Some(event) = self.internal_channel.1.recv() => { - match event { - InternalEvent::JobIngest(job) => { - self.jobs.ingest(&ctx, job).await; - }, - InternalEvent::JobQueue(job) => { - self.jobs.ingest_queue(&ctx, job); - }, - InternalEvent::JobComplete(id) => { - self.jobs.complete(&ctx, id).await; - }, - } - } } } } - // load library database + initialize client with db - pub async fn initializer(&self) { - println!("Initializing..."); - let ctx = self.get_context(); - - if self.state.libraries.len() == 0 { - match library::create(&ctx, None).await { - Ok(library) => println!("Created new library: {:?}", library), - Err(e) => println!("Error creating library: {:?}", e), - } - } else { - for library in self.state.libraries.iter() { - // init database for library - match library::load(&ctx, &library.library_path, &library.library_uuid).await { - Ok(library) => println!("Loaded library: {:?}", library), - Err(e) => println!("Error loading library: {:?}", e), - } - } - } - // init node data within library - match node::LibraryNode::create(&self).await { - Ok(_) => println!("Spacedrive online"), - Err(e) => println!("Error initializing node: {:?}", e), - }; - } async fn exec_command(&mut self, cmd: ClientCommand) -> Result { - println!("Core command: {:?}", cmd); - let ctx = self.get_context(); Ok(match cmd { - // CRUD for locations - ClientCommand::LocCreate { path } => { - let loc = sys::new_location_and_scan(&ctx, &path).await?; - // ctx.queue_job(Box::new(FileIdentifierJob)); - CoreResponse::LocCreate(loc) + ClientCommand::CreateLibrary { name } => { + self.library_manager + .create(LibraryConfig { + name: name.to_string(), + ..Default::default() + }) + .await + .unwrap(); + CoreResponse::Success(()) } - ClientCommand::LocUpdate { id, name } => { - ctx.database - .location() - .find_unique(location::id::equals(id)) - .update(vec![location::name::set(name)]) - .exec() - .await?; + ClientCommand::EditLibrary { + id, + name, + description, + } => { + self.library_manager + .edit_library(id, name, description) + .await + .unwrap(); + CoreResponse::Success(()) + } + ClientCommand::DeleteLibrary { id } => { + self.library_manager.delete_library(id).await.unwrap(); + CoreResponse::Success(()) + } + ClientCommand::LibraryCommand { + library_id, + command, + } => { + let ctx = self.library_manager.get_ctx(library_id).await.unwrap(); + match command { + // CRUD for locations + LibraryCommand::LocCreate { path } => { + let loc = sys::new_location_and_scan(&ctx, &path).await?; + // ctx.queue_job(Box::new(FileIdentifierJob)); + CoreResponse::LocCreate(loc) + } + LibraryCommand::LocUpdate { id, name } => { + ctx.db + .location() + .find_unique(location::id::equals(id)) + .update(vec![location::name::set(name)]) + .exec() + .await?; - CoreResponse::Success(()) - } - ClientCommand::LocDelete { id } => { - sys::delete_location(&ctx, id).await?; - CoreResponse::Success(()) - } - ClientCommand::LocRescan { id } => { - sys::scan_location(&ctx, id, String::new()); - CoreResponse::Success(()) - } - // CRUD for files - ClientCommand::FileReadMetaData { id: _ } => todo!(), - ClientCommand::FileSetNote { id, note } => file::set_note(ctx, id, note).await?, - // ClientCommand::FileEncrypt { id: _, algorithm: _ } => todo!(), - ClientCommand::FileDelete { id } => { - ctx.database - .file() - .find_unique(prisma_file::id::equals(id)) - .delete() - .exec() - .await?; + CoreResponse::Success(()) + } + LibraryCommand::LocDelete { id } => { + sys::delete_location(&ctx, id).await?; + CoreResponse::Success(()) + } + LibraryCommand::LocRescan { id } => { + sys::scan_location(&ctx, id, String::new()).await; + CoreResponse::Success(()) + } + // CRUD for files + LibraryCommand::FileReadMetaData { id: _ } => todo!(), + LibraryCommand::FileSetNote { id, note } => { + file::set_note(ctx, id, note).await? + } + // ClientCommand::FileEncrypt { id: _, algorithm: _ } => todo!(), + LibraryCommand::FileDelete { id } => { + ctx.db + .file() + .find_unique(prisma_file::id::equals(id)) + .delete() + .exec() + .await?; - CoreResponse::Success(()) - } - // CRUD for tags - ClientCommand::TagCreate { name: _, color: _ } => todo!(), - ClientCommand::TagAssign { - file_id: _, - tag_id: _, - } => todo!(), - ClientCommand::TagDelete { id: _ } => todo!(), - // CRUD for libraries - ClientCommand::SysVolumeUnmount { id: _ } => todo!(), - ClientCommand::LibDelete { id: _ } => todo!(), - ClientCommand::TagUpdate { name: _, color: _ } => todo!(), - ClientCommand::GenerateThumbsForLocation { id, path } => { - ctx.spawn_job(Box::new(ThumbnailJob { - location_id: id, - path, - background: false, // fix - })); - CoreResponse::Success(()) - } - // ClientCommand::PurgeDatabase => { - // println!("Purging database..."); - // fs::remove_file(Path::new(&self.state.data_path).join("library.db")).unwrap(); - // CoreResponse::Success(()) - // } - ClientCommand::IdentifyUniqueFiles { id, path } => { - ctx.spawn_job(Box::new(FileIdentifierJob { - location_id: id, - path, - })); - CoreResponse::Success(()) + CoreResponse::Success(()) + } + // CRUD for tags + LibraryCommand::TagCreate { name: _, color: _ } => todo!(), + LibraryCommand::TagAssign { + file_id: _, + tag_id: _, + } => todo!(), + LibraryCommand::TagUpdate { name: _, color: _ } => todo!(), + LibraryCommand::TagDelete { id: _ } => todo!(), + // CRUD for libraries + LibraryCommand::SysVolumeUnmount { id: _ } => todo!(), + LibraryCommand::GenerateThumbsForLocation { id, path } => { + ctx.spawn_job(Box::new(ThumbnailJob { + location_id: id, + path, + background: false, // fix + })) + .await; + CoreResponse::Success(()) + } + LibraryCommand::IdentifyUniqueFiles { id, path } => { + ctx.spawn_job(Box::new(FileIdentifierJob { + location_id: id, + path, + })) + .await; + CoreResponse::Success(()) + } + } } }) } // query sources of data async fn exec_query(&self, query: ClientQuery) -> Result { - let ctx = self.get_context(); Ok(match query { - // return the client state from memory - ClientQuery::NodeGetState => CoreResponse::NodeGetState(self.state.clone()), - // get system volumes without saving to library - ClientQuery::SysGetVolumes => CoreResponse::SysGetVolumes(sys::Volume::get_volumes()?), - ClientQuery::SysGetLocations => { - CoreResponse::SysGetLocations(sys::get_locations(&ctx).await?) - } - // get location from library - ClientQuery::SysGetLocation { id } => { - CoreResponse::SysGetLocation(sys::get_location(&ctx, id).await?) - } - // return contents of a directory for the explorer - ClientQuery::LibGetExplorerDir { - path, - location_id, - limit: _, - } => CoreResponse::LibGetExplorerDir( - file::explorer::open_dir(&ctx, &location_id, &path).await?, + ClientQuery::NodeGetLibraries => CoreResponse::NodeGetLibraries( + self.library_manager.get_all_libraries_config().await, ), - ClientQuery::LibGetTags => todo!(), + ClientQuery::NodeGetState => CoreResponse::NodeGetState(NodeState { + config: self.config.get().await, + data_path: self.config.data_directory().to_str().unwrap().to_string(), + }), + ClientQuery::SysGetVolumes => CoreResponse::SysGetVolumes(sys::Volume::get_volumes()?), ClientQuery::JobGetRunning => { CoreResponse::JobGetRunning(self.jobs.get_running().await) } - ClientQuery::JobGetHistory => { - CoreResponse::JobGetHistory(Jobs::get_history(&ctx).await?) - } - ClientQuery::GetLibraryStatistics => { - CoreResponse::GetLibraryStatistics(library::Statistics::calculate(&ctx).await?) - } ClientQuery::GetNodes => todo!(), + ClientQuery::LibraryQuery { library_id, query } => { + let ctx = match self.library_manager.get_ctx(library_id.clone()).await { + Some(ctx) => ctx, + None => { + println!("Library '{}' not found!", library_id); + return Ok(CoreResponse::Error("Library not found".into())); + } + }; + match query { + LibraryQuery::SysGetLocations => { + CoreResponse::SysGetLocations(sys::get_locations(&ctx).await?) + } + // get location from library + LibraryQuery::SysGetLocation { id } => { + CoreResponse::SysGetLocation(sys::get_location(&ctx, id).await?) + } + // return contents of a directory for the explorer + LibraryQuery::LibGetExplorerDir { + path, + location_id, + limit: _, + } => CoreResponse::LibGetExplorerDir( + file::explorer::open_dir(&ctx, &location_id, &path).await?, + ), + LibraryQuery::LibGetTags => todo!(), + LibraryQuery::JobGetHistory => { + CoreResponse::JobGetHistory(JobManager::get_history(&ctx).await?) + } + LibraryQuery::GetLibraryStatistics => CoreResponse::GetLibraryStatistics( + library::Statistics::calculate(&ctx).await?, + ), + } + } }) } } -// represents an event this library can emit +/// is a command destined for the core #[derive(Serialize, Deserialize, Debug, TS)] #[serde(tag = "key", content = "params")] #[ts(export)] pub enum ClientCommand { + // Libraries + CreateLibrary { + name: String, + }, + EditLibrary { + id: String, + name: Option, + description: Option, + }, + DeleteLibrary { + id: String, + }, + LibraryCommand { + library_id: String, + command: LibraryCommand, + }, +} + +/// is a command destined for a specific library which is loaded into the core. +#[derive(Serialize, Deserialize, Debug, TS)] +#[serde(tag = "key", content = "params")] +#[ts(export)] +pub enum LibraryCommand { // Files FileReadMetaData { id: i32 }, FileSetNote { id: i32, note: Option }, // FileEncrypt { id: i32, algorithm: EncryptionAlgorithm }, FileDelete { id: i32 }, - // Library - LibDelete { id: i32 }, // Tags TagCreate { name: String, color: String }, TagUpdate { name: String, color: String }, @@ -380,15 +364,28 @@ pub enum ClientCommand { IdentifyUniqueFiles { id: i32, path: String }, } -// represents an event this library can emit +/// is a query destined for the core #[derive(Serialize, Deserialize, Debug, TS)] #[serde(tag = "key", content = "params")] #[ts(export)] pub enum ClientQuery { + NodeGetLibraries, NodeGetState, SysGetVolumes, - LibGetTags, JobGetRunning, + GetNodes, + LibraryQuery { + library_id: String, + query: LibraryQuery, + }, +} + +/// is a query destined for a specific library which is loaded into the core. +#[derive(Serialize, Deserialize, Debug, TS)] +#[serde(tag = "key", content = "params")] +#[ts(export)] +pub enum LibraryQuery { + LibGetTags, JobGetHistory, SysGetLocations, SysGetLocation { @@ -400,7 +397,6 @@ pub enum ClientQuery { limit: i32, }, GetLibraryStatistics, - GetNodes, } // represents an event this library can emit @@ -417,11 +413,21 @@ pub enum CoreEvent { DatabaseDisconnected { reason: Option }, } +#[derive(Serialize, Deserialize, Debug, TS)] +#[ts(export)] +pub struct NodeState { + #[serde(flatten)] + pub config: NodeConfig, + pub data_path: String, +} + #[derive(Serialize, Deserialize, Debug, TS)] #[serde(tag = "key", content = "data")] #[ts(export)] pub enum CoreResponse { Success(()), + Error(String), + NodeGetLibraries(Vec), SysGetVolumes(Vec), SysGetLocation(sys::LocationResource), SysGetLocations(Vec), diff --git a/core/src/library/library_config.rs b/core/src/library/library_config.rs new file mode 100644 index 000000000..5d9baf65a --- /dev/null +++ b/core/src/library/library_config.rs @@ -0,0 +1,72 @@ +use std::{ + fs::File, + io::{BufReader, Seek, SeekFrom}, + path::PathBuf, +}; + +use serde::{Deserialize, Serialize}; +use std::io::Write; +use ts_rs::TS; + +use crate::node::ConfigMetadata; + +use super::LibraryManagerError; + +/// LibraryConfig holds the configuration for a specific library. This is stored as a '{uuid}.sdlibrary' file. +#[derive(Debug, Serialize, Deserialize, Clone, TS, Default)] +#[ts(export)] +pub struct LibraryConfig { + #[serde(flatten)] + pub metadata: ConfigMetadata, + /// name is the display name of the library. This is used in the UI and is set by the user. + pub name: String, + /// description is a user set description of the library. This is used in the UI and is set by the user. + pub description: String, +} + +impl LibraryConfig { + /// read will read the configuration from disk and return it. + pub(super) async fn read(file_dir: PathBuf) -> Result { + let mut file = File::open(&file_dir)?; + let base_config: ConfigMetadata = serde_json::from_reader(BufReader::new(&mut file))?; + + Self::migrate_config(base_config.version, file_dir)?; + + file.seek(SeekFrom::Start(0))?; + Ok(serde_json::from_reader(BufReader::new(&mut file))?) + } + + /// save will write the configuration back to disk + pub(super) async fn save( + file_dir: PathBuf, + config: &LibraryConfig, + ) -> Result<(), LibraryManagerError> { + File::create(file_dir) + .map_err(LibraryManagerError::IOError)? + .write_all(serde_json::to_string(config)?.as_bytes()) + .map_err(LibraryManagerError::IOError)?; + Ok(()) + } + + /// migrate_config is a function used to apply breaking changes to the library config file. + fn migrate_config( + current_version: Option, + config_path: PathBuf, + ) -> Result<(), LibraryManagerError> { + match current_version { + None => Err(LibraryManagerError::MigrationError(format!( + "Your Spacedrive library at '{}' is missing the `version` field", + config_path.display() + ))), + _ => Ok(()), + } + } +} + +// used to return to the frontend with uuid context +#[derive(Serialize, Deserialize, Debug, TS)] +#[ts(export)] +pub struct LibraryConfigWrapped { + pub uuid: String, + pub config: LibraryConfig, +} diff --git a/core/src/library/library_ctx.rs b/core/src/library/library_ctx.rs new file mode 100644 index 000000000..50bc5ea94 --- /dev/null +++ b/core/src/library/library_ctx.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use uuid::Uuid; + +use crate::{job::Job, node::NodeConfigManager, prisma::PrismaClient, CoreEvent, NodeContext}; + +use super::LibraryConfig; + +/// LibraryContext holds context for a library which can be passed around the application. +#[derive(Clone)] +pub struct LibraryContext { + /// id holds the ID of the current library. + pub id: Uuid, + /// config holds the configuration of the current library. + pub config: LibraryConfig, + /// db holds the database client for the current library. + pub db: Arc, + /// node_local_id holds the local ID of the node which is running the library. + pub node_local_id: i32, + /// node_context holds the node context for the node which this library is running on. + pub(super) node_context: NodeContext, +} + +impl LibraryContext { + pub(crate) async fn spawn_job(&self, job: Box) { + self.node_context.jobs.clone().ingest(self, job).await; + } + + pub(crate) async fn queue_job(&self, job: Box) { + self.node_context.jobs.ingest_queue(self, job).await; + } + + pub(crate) async fn emit(&self, event: CoreEvent) { + self.node_context + .event_sender + .send(event) + .await + .unwrap_or_else(|e| { + println!("Failed to emit event. {:?}", e); + }); + } + + pub(crate) fn config(&self) -> Arc { + self.node_context.config.clone() + } +} diff --git a/core/src/library/library_manager.rs b/core/src/library/library_manager.rs new file mode 100644 index 000000000..a4da40acd --- /dev/null +++ b/core/src/library/library_manager.rs @@ -0,0 +1,271 @@ +use std::{ + env, fs, io, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, +}; + +use thiserror::Error; +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::{ + node::Platform, + prisma::{self, node}, + util::db::load_and_migrate, + ClientQuery, CoreEvent, NodeContext, +}; + +use super::{LibraryConfig, LibraryConfigWrapped, LibraryContext}; + +/// LibraryManager is a singleton that manages all libraries for a node. +pub struct LibraryManager { + /// libraries_dir holds the path to the directory where libraries are stored. + libraries_dir: PathBuf, + /// libraries holds the list of libraries which are currently loaded into the node. + libraries: RwLock>, + /// node_context holds the context for the node which this library manager is running on. + node_context: NodeContext, +} + +#[derive(Error, Debug)] +pub enum LibraryManagerError { + #[error("error saving or loading the config from the filesystem")] + IOError(#[from] io::Error), + #[error("error serializing or deserializing the JSON in the config file")] + JsonError(#[from] serde_json::Error), + #[error("Database error")] + DatabaseError(#[from] prisma::QueryError), + #[error("Library not found error")] + LibraryNotFoundError, + #[error("error migrating the config file")] + MigrationError(String), + #[error("failed to parse uuid")] + UuidError(#[from] uuid::Error), +} + +impl LibraryManager { + pub(crate) async fn new( + libraries_dir: PathBuf, + node_context: NodeContext, + ) -> Result, LibraryManagerError> { + fs::create_dir_all(&libraries_dir)?; + + let mut libraries = Vec::new(); + for entry in fs::read_dir(&libraries_dir)? + .into_iter() + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry.path().is_file() + && entry + .path() + .extension() + .map(|v| &*v == "sdlibrary") + .unwrap_or(false) + }) { + let config_path = entry.path(); + let library_id = match Path::new(&config_path) + .file_stem() + .map(|v| v.to_str().map(|v| Uuid::from_str(v))) + { + Some(Some(Ok(id))) => id, + _ => { + println!("Attempted to load library from path '{}' but it has an invalid filename. Skipping...", config_path.display()); + continue; + } + }; + + let db_path = config_path.clone().with_extension("db"); + if !db_path.exists() { + println!( + "Found library '{}' but no matching database file was found. Skipping...", + config_path.display() + ); + continue; + } + + let config = LibraryConfig::read(config_path).await?; + libraries.push( + Self::load( + library_id, + db_path.to_str().unwrap(), + config, + node_context.clone(), + ) + .await?, + ); + } + + let this = Arc::new(Self { + libraries: RwLock::new(libraries), + libraries_dir, + node_context, + }); + + // TODO: Remove this before merging PR -> Currently it exists to make the app usable + if this.libraries.read().await.len() == 0 { + this.create(LibraryConfig { + name: "My Default Library".into(), + ..Default::default() + }) + .await + .unwrap(); + } + + Ok(this) + } + + /// create creates a new library with the given config and mounts it into the running [LibraryManager]. + pub(crate) async fn create(&self, config: LibraryConfig) -> Result<(), LibraryManagerError> { + let id = Uuid::new_v4(); + LibraryConfig::save( + Path::new(&self.libraries_dir).join(format!("{}.sdlibrary", id.to_string())), + &config, + ) + .await?; + + let library = Self::load( + id, + &Path::new(&self.libraries_dir) + .join(format!("{}.db", id.to_string())) + .to_str() + .unwrap(), + config, + self.node_context.clone(), + ) + .await?; + + self.libraries.write().await.push(library); + + self.node_context + .emit(CoreEvent::InvalidateQuery(ClientQuery::NodeGetLibraries)) + .await; + + Ok(()) + } + + pub(crate) async fn get_all_libraries_config(&self) -> Vec { + self.libraries + .read() + .await + .iter() + .map(|lib| LibraryConfigWrapped { + config: lib.config.clone(), + uuid: lib.id.to_string(), + }) + .collect() + } + + pub(crate) async fn edit_library( + &self, + id: String, + name: Option, + description: Option, + ) -> Result<(), LibraryManagerError> { + // check library is valid + let mut libraries = self.libraries.write().await; + let library = libraries + .iter_mut() + .find(|lib| lib.id == Uuid::from_str(&id).unwrap()) + .ok_or(LibraryManagerError::LibraryNotFoundError)?; + + // update the library + if let Some(name) = name { + library.config.name = name; + } + if let Some(description) = description { + library.config.description = description; + } + + LibraryConfig::save( + Path::new(&self.libraries_dir).join(format!("{}.sdlibrary", id.to_string())), + &library.config, + ) + .await?; + + self.node_context + .emit(CoreEvent::InvalidateQuery(ClientQuery::NodeGetLibraries)) + .await; + Ok(()) + } + + pub async fn delete_library(&self, id: String) -> Result<(), LibraryManagerError> { + let mut libraries = self.libraries.write().await; + + let id = Uuid::parse_str(&id)?; + + let library = libraries + .iter() + .find(|l| l.id == id) + .ok_or(LibraryManagerError::LibraryNotFoundError)?; + + fs::remove_file( + Path::new(&self.libraries_dir).join(format!("{}.db", library.id.to_string())), + )?; + fs::remove_file( + Path::new(&self.libraries_dir).join(format!("{}.sdlibrary", library.id.to_string())), + )?; + + libraries.retain(|l| l.id != id); + + self.node_context + .emit(CoreEvent::InvalidateQuery(ClientQuery::NodeGetLibraries)) + .await; + Ok(()) + } + + // get_ctx will return the library context for the given library id. + pub(crate) async fn get_ctx(&self, library_id: String) -> Option { + self.libraries + .read() + .await + .iter() + .find(|lib| lib.id.to_string() == library_id) + .map(|v| v.clone()) + } + + /// load the library from a given path + pub(crate) async fn load( + id: Uuid, + db_path: &str, + config: LibraryConfig, + node_context: NodeContext, + ) -> Result { + let db = Arc::new( + load_and_migrate(&format!("file:{}", db_path)) + .await + .unwrap(), + ); + + let node_config = node_context.config.get().await; + + let platform = match env::consts::OS { + "windows" => Platform::Windows, + "macos" => Platform::MacOS, + "linux" => Platform::Linux, + _ => Platform::Unknown, + }; + + let node_data = db + .node() + .upsert( + node::pub_id::equals(id.to_string()), + ( + node::pub_id::set(id.to_string()), + node::name::set(node_config.name.clone()), + vec![node::platform::set(platform as i32)], + ), + vec![node::name::set(node_config.name.clone())], + ) + .exec() + .await?; + + Ok(LibraryContext { + id, + config, + db, + node_local_id: node_data.id, + node_context, + }) + } +} diff --git a/core/src/library/loader.rs b/core/src/library/loader.rs deleted file mode 100644 index c2fa47beb..000000000 --- a/core/src/library/loader.rs +++ /dev/null @@ -1,99 +0,0 @@ -use uuid::Uuid; - -use crate::node::{get_nodestate, LibraryState}; -use crate::prisma::library; -use crate::util::db::{run_migrations, DatabaseError}; -use crate::CoreContext; - -pub static LIBRARY_DB_NAME: &str = "library.db"; -pub static DEFAULT_NAME: &str = "My Library"; - -pub fn get_library_path(data_path: &str) -> String { - let path = data_path.to_owned(); - format!("{}/{}", path, LIBRARY_DB_NAME) -} - -// pub async fn get(core: &Node) -> Result { -// let config = get_nodestate(); -// let db = &core.database; - -// let library_state = config.get_current_library(); - -// println!("{:?}", library_state); - -// // get library from db -// let library = match db -// .library() -// .find_unique(library::pub_id::equals(library_state.library_uuid.clone())) -// .exec() -// .await? -// { -// Some(library) => Ok(library), -// None => { -// // update config library state to offline -// // config.libraries - -// Err(anyhow::anyhow!("library_not_found")) -// } -// }; - -// Ok(library.unwrap()) -// } - -pub async fn load( - ctx: &CoreContext, - library_path: &str, - library_id: &str, -) -> Result<(), DatabaseError> { - let mut config = get_nodestate(); - - println!("Initializing library: {} {}", &library_id, library_path); - - if config.current_library_uuid != library_id { - config.current_library_uuid = library_id.to_string(); - config.save(); - } - // create connection with library database & run migrations - run_migrations(&ctx).await?; - // if doesn't exist, mark as offline - Ok(()) -} - -pub async fn create(ctx: &CoreContext, name: Option) -> Result<(), ()> { - let mut config = get_nodestate(); - - let uuid = Uuid::new_v4().to_string(); - - println!("Creating library {:?}, UUID: {:?}", name, uuid); - - let library_state = LibraryState { - library_uuid: uuid.clone(), - library_path: get_library_path(&config.data_path), - ..LibraryState::default() - }; - - run_migrations(&ctx).await.unwrap(); - - config.libraries.push(library_state); - - config.current_library_uuid = uuid; - - config.save(); - - let db = &ctx.database; - - let library = db - .library() - .create( - library::pub_id::set(config.current_library_uuid), - library::name::set(name.unwrap_or(DEFAULT_NAME.into())), - vec![], - ) - .exec() - .await - .unwrap(); - - println!("library created in database: {:?}", library); - - Ok(()) -} diff --git a/core/src/library/mod.rs b/core/src/library/mod.rs index 17dc6db10..23aed8efa 100644 --- a/core/src/library/mod.rs +++ b/core/src/library/mod.rs @@ -1,7 +1,11 @@ -mod loader; +mod library_config; +mod library_ctx; +mod library_manager; mod statistics; -pub use loader::*; +pub use library_config::*; +pub use library_ctx::*; +pub use library_manager::*; pub use statistics::*; use thiserror::Error; diff --git a/core/src/library/statistics.rs b/core/src/library/statistics.rs index f866999b5..f1df98dd0 100644 --- a/core/src/library/statistics.rs +++ b/core/src/library/statistics.rs @@ -1,15 +1,10 @@ -use crate::{ - node::get_nodestate, - prisma::{library, library_statistics::*}, - sys::Volume, - CoreContext, -}; +use crate::{prisma::statistics::*, sys::Volume}; use fs_extra::dir::get_size; use serde::{Deserialize, Serialize}; use std::fs; use ts_rs::TS; -use super::LibraryError; +use super::{LibraryContext, LibraryError}; #[derive(Debug, Serialize, Deserialize, TS, Clone)] #[ts(export)] @@ -52,14 +47,11 @@ impl Default for Statistics { } impl Statistics { - pub async fn retrieve(ctx: &CoreContext) -> Result { - let config = get_nodestate(); - let db = &ctx.database; - let library_data = config.get_current_library(); - - let library_statistics_db = match db - .library_statistics() - .find_unique(id::equals(library_data.library_id)) + pub async fn retrieve(ctx: &LibraryContext) -> Result { + let library_statistics_db = match ctx + .db + .statistics() + .find_unique(id::equals(ctx.node_local_id)) .exec() .await? { @@ -70,31 +62,11 @@ impl Statistics { Ok(library_statistics_db.into()) } - pub async fn calculate(ctx: &CoreContext) -> Result { - let config = get_nodestate(); - let db = &ctx.database; - // get library from client state - let library_data = config.get_current_library(); - println!( - "Calculating library statistics {:?}", - library_data.library_uuid - ); - // get library from db - let library = db - .library() - .find_unique(library::pub_id::equals( - library_data.library_uuid.to_string(), - )) - .exec() - .await?; - - if library.is_none() { - return Err(LibraryError::LibraryNotFound); - } - - let library_statistics = db - .library_statistics() - .find_unique(id::equals(library_data.library_id)) + pub async fn calculate(ctx: &LibraryContext) -> Result { + let _statistics = ctx + .db + .statistics() + .find_unique(id::equals(ctx.node_local_id)) .exec() .await?; @@ -113,14 +85,15 @@ impl Statistics { } } - let library_db_size = match fs::metadata(library_data.library_path.as_str()) { + let library_db_size = match fs::metadata(ctx.config().data_directory()) { Ok(metadata) => metadata.len(), Err(_) => 0, }; - println!("{:?}", library_statistics); + let mut thumbsnails_dir = ctx.config().data_directory(); + thumbsnails_dir.push("thumbnails"); - let thumbnail_folder_size = get_size(&format!("{}/{}", config.data_path, "thumbnails")); + let thumbnail_folder_size = get_size(&thumbsnails_dir); let statistics = Statistics { library_db_size: library_db_size.to_string(), @@ -130,18 +103,11 @@ impl Statistics { ..Statistics::default() }; - let library_local_id = match library { - Some(library) => library.id, - None => library_data.library_id, - }; - - db.library_statistics() + ctx.db + .statistics() .upsert( - library_id::equals(library_local_id), - ( - library_id::set(library_local_id), - vec![library_db_size::set(statistics.library_db_size.clone())], - ), + id::equals(1), + vec![library_db_size::set(statistics.library_db_size.clone())], vec![ total_file_count::set(statistics.total_file_count.clone()), total_bytes_used::set(statistics.total_bytes_used.clone()), diff --git a/core/src/node/config.rs b/core/src/node/config.rs new file mode 100644 index 000000000..c45833a1c --- /dev/null +++ b/core/src/node/config.rs @@ -0,0 +1,149 @@ +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::{self, BufReader, Seek, SeekFrom, Write}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use thiserror::Error; +use tokio::sync::{RwLock, RwLockWriteGuard}; +use ts_rs::TS; +use uuid::Uuid; + +/// NODE_STATE_CONFIG_NAME is the name of the file which stores the NodeState +pub const NODE_STATE_CONFIG_NAME: &str = "node_state.sdconfig"; + +/// ConfigMetadata is a part of node configuration that is loaded before the main configuration and contains information about the schema of the config. +/// This allows us to migrate breaking changes to the config format between Spacedrive releases. +#[derive(Debug, Serialize, Deserialize, Clone, TS)] +#[ts(export)] +pub struct ConfigMetadata { + /// version of Spacedrive. Determined from `CARGO_PKG_VERSION` environment variable. + pub version: Option, +} + +impl Default for ConfigMetadata { + fn default() -> Self { + Self { + version: Some(env!("CARGO_PKG_VERSION").into()), + } + } +} + +/// NodeConfig is the configuration for a node. This is shared between all libraries and is stored in a JSON file on disk. +#[derive(Debug, Serialize, Deserialize, Clone, TS)] +#[ts(export)] +pub struct NodeConfig { + #[serde(flatten)] + pub metadata: ConfigMetadata, + /// id is a unique identifier for the current node. Each node has a public identifier (this one) and is given a local id for each library (done within the library code). + pub id: Uuid, + /// name is the display name of the current node. This is set by the user and is shown in the UI. // TODO: Length validation so it can fit in DNS record + pub name: String, + // the port this node uses for peer to peer communication. By default a random free port will be chosen each time the application is started. + pub p2p_port: Option, +} + +#[derive(Error, Debug)] +pub enum NodeConfigError { + #[error("error saving or loading the config from the filesystem")] + IOError(#[from] io::Error), + #[error("error serializing or deserializing the JSON in the config file")] + JsonError(#[from] serde_json::Error), + #[error("error migrating the config file")] + MigrationError(String), +} + +impl NodeConfig { + fn default() -> Self { + NodeConfig { + id: Uuid::new_v4(), + name: match hostname::get() { + Ok(hostname) => hostname.to_string_lossy().into_owned(), + Err(err) => { + eprintln!("Falling back to default node name as an error occurred getting your systems hostname: '{}'", err); + "my-spacedrive".into() + } + }, + p2p_port: None, + metadata: ConfigMetadata { + version: Some(env!("CARGO_PKG_VERSION").into()), + }, + } + } +} + +pub struct NodeConfigManager(RwLock, PathBuf); + +impl NodeConfigManager { + /// new will create a new NodeConfigManager with the given path to the config file. + pub(crate) async fn new(data_path: PathBuf) -> Result, NodeConfigError> { + Ok(Arc::new(Self( + RwLock::new(Self::read(&data_path).await?), + data_path, + ))) + } + + /// get will return the current NodeConfig in a read only state. + pub(crate) async fn get(&self) -> NodeConfig { + self.0.read().await.clone() + } + + /// data_directory returns the path to the directory storing the configuration data. + pub(crate) fn data_directory(&self) -> PathBuf { + self.1.clone() + } + + /// write allows the user to update the configuration. This is done in a closure while a Mutex lock is held so that the user can't cause a race condition if the config were to be updated in multiple parts of the app at the same time. + #[allow(unused)] + pub(crate) async fn write)>( + &self, + mutation_fn: F, + ) -> Result { + mutation_fn(self.0.write().await); + let config = self.0.read().await; + Self::save(&self.1, &config).await?; + Ok(config.clone()) + } + + /// read will read the configuration from disk and return it. + async fn read(base_path: &PathBuf) -> Result { + let path = Path::new(base_path).join(NODE_STATE_CONFIG_NAME); + + match path.exists() { + true => { + let mut file = File::open(&path)?; + let base_config: ConfigMetadata = + serde_json::from_reader(BufReader::new(&mut file))?; + + Self::migrate_config(base_config.version, path)?; + + file.seek(SeekFrom::Start(0))?; + Ok(serde_json::from_reader(BufReader::new(&mut file))?) + } + false => { + let config = NodeConfig::default(); + Self::save(base_path, &config).await?; + Ok(config) + } + } + } + + /// save will write the configuration back to disk + async fn save(base_path: &PathBuf, config: &NodeConfig) -> Result<(), NodeConfigError> { + let path = Path::new(base_path).join(NODE_STATE_CONFIG_NAME); + File::create(path)?.write_all(serde_json::to_string(config)?.as_bytes())?; + Ok(()) + } + + /// migrate_config is a function used to apply breaking changes to the config file. + fn migrate_config( + current_version: Option, + config_path: PathBuf, + ) -> Result<(), NodeConfigError> { + match current_version { + None => { + Err(NodeConfigError::MigrationError(format!("Your Spacedrive config file stored at '{}' is missing the `version` field. If you just upgraded please delete the file and restart Spacedrive! Please note this upgrade will stop using your old 'library.db' as the folder structure has changed.", config_path.display()))) + } + _ => Ok(()), + } + } +} diff --git a/core/src/node/mod.rs b/core/src/node/mod.rs index 52bed0ef9..502a64f15 100644 --- a/core/src/node/mod.rs +++ b/core/src/node/mod.rs @@ -1,17 +1,10 @@ -use crate::{ - prisma::{self, node}, - Node, -}; use chrono::{DateTime, Utc}; use int_enum::IntEnum; use serde::{Deserialize, Serialize}; -use std::env; -use thiserror::Error; use ts_rs::TS; - -mod state; - -pub use state::*; +mod config; +use crate::prisma::node; +pub use config::*; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] @@ -44,65 +37,3 @@ pub enum Platform { IOS = 4, Android = 5, } - -impl LibraryNode { - pub async fn create(node: &Node) -> Result<(), NodeError> { - println!("Creating node..."); - let mut config = state::get_nodestate(); - - let db = &node.database; - - let hostname = match hostname::get() { - Ok(hostname) => hostname.to_str().unwrap_or_default().to_owned(), - Err(_) => "unknown".to_owned(), - }; - - let platform = match env::consts::OS { - "windows" => Platform::Windows, - "macos" => Platform::MacOS, - "linux" => Platform::Linux, - _ => Platform::Unknown, - }; - - let _node = match db - .node() - .find_unique(node::pub_id::equals(config.node_pub_id.clone())) - .exec() - .await? - { - Some(node) => node, - None => { - db.node() - .create( - node::pub_id::set(config.node_pub_id.clone()), - node::name::set(hostname.clone()), - vec![node::platform::set(platform as i32)], - ) - .exec() - .await? - } - }; - - config.node_name = hostname; - config.node_id = _node.id; - config.save(); - - println!("node: {:?}", &_node); - - Ok(()) - } - - // pub async fn get_nodes(ctx: &CoreContext) -> Result, NodeError> { - // let db = &ctx.database; - - // let _node = db.node().find_many(vec![]).exec().await?; - - // Ok(_node) - // } -} - -#[derive(Error, Debug)] -pub enum NodeError { - #[error("Database error")] - DatabaseError(#[from] prisma::QueryError), -} diff --git a/core/src/node/state.rs b/core/src/node/state.rs deleted file mode 100644 index b7124686f..000000000 --- a/core/src/node/state.rs +++ /dev/null @@ -1,107 +0,0 @@ -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::io::{BufReader, Write}; -use std::sync::RwLock; -use ts_rs::TS; -use uuid::Uuid; - -#[derive(Debug, Serialize, Deserialize, Clone, Default, TS)] -#[ts(export)] -pub struct NodeState { - pub node_pub_id: String, - pub node_id: i32, - pub node_name: String, - // config path is stored as struct can exist only in memory during startup and be written to disk later without supplying path - pub data_path: String, - // the port this node uses to listen for incoming connections - pub tcp_port: u32, - // all the libraries loaded by this node - pub libraries: Vec, - // used to quickly find the default library - pub current_library_uuid: String, -} - -pub static NODE_STATE_CONFIG_NAME: &str = "node_state.json"; - -#[derive(Debug, Serialize, Deserialize, Clone, Default, TS)] -#[ts(export)] -pub struct LibraryState { - pub library_uuid: String, - pub library_id: i32, - pub library_path: String, - pub offline: bool, -} - -// global, thread-safe storage for node state -lazy_static! { - static ref CONFIG: RwLock> = RwLock::new(None); -} - -pub fn get_nodestate() -> NodeState { - match CONFIG.read() { - Ok(guard) => guard.clone().unwrap_or(NodeState::default()), - Err(_) => return NodeState::default(), - } -} - -impl NodeState { - pub fn new(data_path: &str, node_name: &str) -> Result { - let uuid = Uuid::new_v4().to_string(); - // create struct and assign defaults - let config = Self { - node_pub_id: uuid, - data_path: data_path.to_string(), - node_name: node_name.to_string(), - ..Default::default() - }; - Ok(config) - } - - pub fn save(&self) { - self.write_memory(); - // only write to disk if config path is set - if !&self.data_path.is_empty() { - let config_path = format!("{}/{}", &self.data_path, NODE_STATE_CONFIG_NAME); - let mut file = fs::File::create(config_path).unwrap(); - let json = serde_json::to_string(&self).unwrap(); - file.write_all(json.as_bytes()).unwrap(); - } - } - - pub fn read_disk(&mut self) -> Result<(), ()> { - let config_path = format!("{}/{}", &self.data_path, NODE_STATE_CONFIG_NAME); - - // open the file and parse json - match fs::File::open(config_path) { - Ok(file) => { - let reader = BufReader::new(file); - let data = serde_json::from_reader(reader).unwrap(); - // assign to self - *self = data; - } - _ => {} - } - Ok(()) - } - - fn write_memory(&self) { - let mut writeable = CONFIG.write().unwrap(); - *writeable = Some(self.clone()); - } - - pub fn get_current_library(&self) -> LibraryState { - match self - .libraries - .iter() - .find(|lib| lib.library_uuid == self.current_library_uuid) - { - Some(lib) => lib.clone(), - None => LibraryState::default(), - } - } - - pub fn get_current_library_db_path(&self) -> String { - format!("{}/library.db", &self.get_current_library().library_path) - } -} diff --git a/core/src/sys/locations.rs b/core/src/sys/locations.rs index 0b4dafac3..220bacecb 100644 --- a/core/src/sys/locations.rs +++ b/core/src/sys/locations.rs @@ -1,11 +1,10 @@ use crate::{ - encode::ThumbnailJob, file::{cas::FileIdentifierJob, indexer::IndexerJob}, - node::{get_nodestate, LibraryNode}, + library::LibraryContext, + node::LibraryNode, prisma::{file_path, location}, - ClientQuery, CoreContext, CoreEvent, + ClientQuery, CoreEvent, LibraryQuery, }; -use prisma_client_rust::{raw, PrismaValue}; use serde::{Deserialize, Serialize}; use std::{fs, io, io::Write, path::Path}; use thiserror::Error; @@ -66,13 +65,12 @@ static DOTFILE_NAME: &str = ".spacedrive"; // } pub async fn get_location( - ctx: &CoreContext, + ctx: &LibraryContext, location_id: i32, ) -> Result { - let db = &ctx.database; - // get location by location_id from db and include location_paths - let location = match db + let location = match ctx + .db .location() .find_unique(location::id::equals(location_id)) .exec() @@ -84,9 +82,11 @@ pub async fn get_location( Ok(location.into()) } -pub fn scan_location(ctx: &CoreContext, location_id: i32, path: String) { - ctx.spawn_job(Box::new(IndexerJob { path: path.clone() })); - ctx.queue_job(Box::new(FileIdentifierJob { location_id, path })); +pub async fn scan_location(ctx: &LibraryContext, location_id: i32, path: String) { + ctx.spawn_job(Box::new(IndexerJob { path: path.clone() })) + .await; + ctx.queue_job(Box::new(FileIdentifierJob { location_id, path })) + .await; // TODO: make a way to stop jobs so this can be canceled without rebooting app // ctx.queue_job(Box::new(ThumbnailJob { // location_id, @@ -96,18 +96,18 @@ pub fn scan_location(ctx: &CoreContext, location_id: i32, path: String) { } pub async fn new_location_and_scan( - ctx: &CoreContext, + ctx: &LibraryContext, path: &str, ) -> Result { let location = create_location(&ctx, path).await?; - scan_location(&ctx, location.id, path.to_string()); + scan_location(&ctx, location.id, path.to_string()).await; Ok(location) } -pub async fn get_locations(ctx: &CoreContext) -> Result, SysError> { - let db = &ctx.database; +pub async fn get_locations(ctx: &LibraryContext) -> Result, SysError> { + let db = &ctx.db; let locations = db .location() @@ -125,10 +125,10 @@ pub async fn get_locations(ctx: &CoreContext) -> Result, S Ok(locations) } -pub async fn create_location(ctx: &CoreContext, path: &str) -> Result { - let db = &ctx.database; - let config = get_nodestate(); - +pub async fn create_location( + ctx: &LibraryContext, + path: &str, +) -> Result { // check if we have access to this location if !Path::new(path).exists() { Err(LocationError::NotFound(path.to_string()))?; @@ -155,7 +155,8 @@ pub async fn create_location(ctx: &CoreContext, path: &str) -> Result Result Result Result Result Err(LocationError::DotfileWriteFailure(e, path.to_string()))?, } - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::SysGetLocations)) - .await; + // ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::SysGetLocations)) + // .await; location } @@ -220,8 +222,8 @@ pub async fn create_location(ctx: &CoreContext, path: &str) -> Result Result<(), SysError> { - let db = &ctx.database; +pub async fn delete_location(ctx: &LibraryContext, location_id: i32) -> Result<(), SysError> { + let db = &ctx.db; db.file_path() .find_many(vec![file_path::location_id::equals(Some(location_id))]) @@ -235,8 +237,11 @@ pub async fn delete_location(ctx: &CoreContext, location_id: i32) -> Result<(), .exec() .await?; - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::SysGetLocations)) - .await; + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::SysGetLocations, + })) + .await; println!("Location {} deleted", location_id); diff --git a/core/src/sys/volumes.rs b/core/src/sys/volumes.rs index 6a043d991..3268787a7 100644 --- a/core/src/sys/volumes.rs +++ b/core/src/sys/volumes.rs @@ -1,5 +1,5 @@ // use crate::native; -use crate::{node::get_nodestate, prisma::volume::*}; +use crate::{library::LibraryContext, prisma::volume::*}; use serde::{Deserialize, Serialize}; use ts_rs::TS; // #[cfg(not(target_os = "macos"))] @@ -7,8 +7,6 @@ use std::process::Command; // #[cfg(not(target_os = "macos"))] use sysinfo::{DiskExt, System, SystemExt}; -use crate::CoreContext; - use super::SysError; #[derive(Serialize, Deserialize, Debug, Default, Clone, TS)] @@ -26,23 +24,21 @@ pub struct Volume { } impl Volume { - pub async fn save(ctx: &CoreContext) -> Result<(), SysError> { - let db = &ctx.database; - let config = get_nodestate(); - + pub async fn save(ctx: &LibraryContext) -> Result<(), SysError> { let volumes = Self::get_volumes()?; // enter all volumes associate with this client add to db for volume in volumes { - db.volume() + ctx.db + .volume() .upsert( node_id_mount_point_name( - config.node_id.clone(), + ctx.node_local_id.clone(), volume.mount_point.to_string(), volume.name.to_string(), ), ( - node_id::set(config.node_id), + node_id::set(ctx.node_local_id), name::set(volume.name), mount_point::set(volume.mount_point), vec![ diff --git a/core/src/util/db.rs b/core/src/util/db.rs index d0b31b2c8..0d1b3c33b 100644 --- a/core/src/util/db.rs +++ b/core/src/util/db.rs @@ -1,159 +1,123 @@ use crate::prisma::{self, migration, PrismaClient}; -use crate::CoreContext; use data_encoding::HEXLOWER; use include_dir::{include_dir, Dir}; -use prisma_client_rust::raw; -use ring::digest::{Context, Digest, SHA256}; -use std::ffi::OsStr; -use std::io::{self, BufReader, Read}; +use prisma_client_rust::{raw, NewClientError}; +use ring::digest::{Context, SHA256}; use thiserror::Error; const INIT_MIGRATION: &str = include_str!("../../prisma/migrations/migration_table/migration.sql"); static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/prisma/migrations"); +/// MigrationError represents an error that occurring while opening a initialising and running migrations on the database. #[derive(Error, Debug)] -pub enum DatabaseError { - #[error("Unable to initialize the Prisma client")] - ClientError(#[from] prisma::NewClientError), +pub enum MigrationError { + #[error("An error occurred while initialising a new database connection")] + DatabaseIntialisation(#[from] NewClientError), + #[error("An error occurred with the database while applying migrations")] + DatabaseError(#[from] prisma_client_rust::queries::Error), + #[error("An error occured reading the embedded migration files. {0}. Please report to Spacedrive developers!")] + InvalidEmbeddedMigration(&'static str), } -pub async fn create_connection(path: &str) -> Result { - println!("Creating database connection: {:?}", path); - let client = prisma::new_client_with_url(&format!("file:{}", &path)).await?; +/// load_and_migrate will load the database from the given path and migrate it to the latest version of the schema. +pub async fn load_and_migrate(db_url: &str) -> Result { + let client = prisma::new_client_with_url(db_url).await?; - Ok(client) -} - -pub fn sha256_digest(mut reader: R) -> Result { - let mut context = Context::new(&SHA256); - let mut buffer = [0; 1024]; - loop { - let count = reader.read(&mut buffer)?; - if count == 0 { - break; - } - context.update(&buffer[..count]); - } - Ok(context.finish()) -} - -pub async fn run_migrations(ctx: &CoreContext) -> Result<(), DatabaseError> { - let client = &ctx.database; - - match client + let migrations_table_missing = client ._query_raw::(raw!( "SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'" )) - .await - { - Ok(data) => { - if data.len() == 0 { - // execute migration - match client._execute_raw(raw!(INIT_MIGRATION)).await { - Ok(_) => {} - Err(e) => { - println!("Failed to create migration table: {}", e); - } - }; + .await? + .len() == 0; - let value: Vec = client - ._query_raw(raw!( - "SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'" - )) - .await - .unwrap(); + if migrations_table_missing { + client._execute_raw(raw!(INIT_MIGRATION)).await?; + } - #[cfg(debug_assertions)] - println!("Migration table created: {:?}", value); - } - - let mut migration_subdirs = MIGRATIONS_DIR - .dirs() - .filter(|subdir| { - subdir - .path() - .file_name() - .map(|name| name != OsStr::new("migration_table")) - .unwrap_or(false) + let mut migration_directories = MIGRATIONS_DIR + .dirs() + .map(|dir| { + dir.path() + .file_name() + .ok_or(MigrationError::InvalidEmbeddedMigration( + "File has malformed name", + )) + .and_then(|name| { + name.to_str() + .ok_or_else(|| { + MigrationError::InvalidEmbeddedMigration( + "File name contains malformed characters", + ) + }) + .map(|name| (name, dir)) }) - .collect::>(); + }) + .filter_map(|v| match v { + Ok((name, _)) if name == "migration_table" => None, + Ok((name, dir)) => match name[..14].parse::() { + Ok(timestamp) => Some(Ok((name, timestamp, dir))), + Err(_) => Some(Err(MigrationError::InvalidEmbeddedMigration( + "File name is incorrectly formatted", + ))), + }, + Err(v) => Some(Err(v)), + }) + .collect::, _>>()?; - migration_subdirs.sort_by(|a, b| { - let a_name = a.path().file_name().unwrap().to_str().unwrap(); - let b_name = b.path().file_name().unwrap().to_str().unwrap(); + // We sort the migrations so they are always applied in the correct order + migration_directories.sort_by(|(_, a_time, _), (_, b_time, _)| a_time.cmp(&b_time)); - let a_time = a_name[..14].parse::().unwrap(); - let b_time = b_name[..14].parse::().unwrap(); + for (name, _, dir) in migration_directories { + let migration_file_raw = dir + .get_file(dir.path().join("./migration.sql")) + .ok_or(MigrationError::InvalidEmbeddedMigration( + "Failed to find 'migration.sql' file in '{}' migration subdirectory", + ))? + .contents_utf8() + .ok_or( + MigrationError::InvalidEmbeddedMigration( + "Failed to open the contents of 'migration.sql' file in '{}' migration subdirectory", + ) + )?; - a_time.cmp(&b_time) - }); + // Generate SHA256 checksum of migration + let mut checksum = Context::new(&SHA256); + checksum.update(migration_file_raw.as_bytes()); + let checksum = HEXLOWER.encode(checksum.finish().as_ref()); - for subdir in migration_subdirs { - println!("{:?}", subdir.path()); - let migration_file = subdir - .get_file(subdir.path().join("./migration.sql")) - .unwrap(); - let migration_sql = migration_file.contents_utf8().unwrap(); + // get existing migration by checksum, if it doesn't exist run the migration + if client + .migration() + .find_unique(migration::checksum::equals(checksum.clone())) + .exec() + .await? + .is_none() + { + // Create migration record + client + .migration() + .create( + migration::name::set(name.to_string()), + migration::checksum::set(checksum.clone()), + vec![], + ) + .exec() + .await?; - let digest = sha256_digest(BufReader::new(migration_file.contents())).unwrap(); - // create a lowercase hash from - let checksum = HEXLOWER.encode(digest.as_ref()); - let name = subdir.path().file_name().unwrap().to_str().unwrap(); - - // get existing migration by checksum, if it doesn't exist run the migration - let existing_migration = client + // Split the migrations file up into each individual step and apply them all + let steps = migration_file_raw.split(";").collect::>(); + let steps = &steps[0..steps.len() - 1]; + for (i, step) in steps.iter().enumerate() { + client._execute_raw(raw!(*step)).await?; + client .migration() .find_unique(migration::checksum::equals(checksum.clone())) + .update(vec![migration::steps_applied::set(i as i32 + 1)]) .exec() - .await - .unwrap(); - - if existing_migration.is_none() { - #[cfg(debug_assertions)] - println!("Running migration: {}", name); - - let steps = migration_sql.split(";").collect::>(); - let steps = &steps[0..steps.len() - 1]; - - client - .migration() - .create( - migration::name::set(name.to_string()), - migration::checksum::set(checksum.clone()), - vec![], - ) - .exec() - .await - .unwrap(); - - for (i, step) in steps.iter().enumerate() { - match client._execute_raw(raw!(*step)).await { - Ok(_) => { - client - .migration() - .find_unique(migration::checksum::equals(checksum.clone())) - .update(vec![migration::steps_applied::set(i as i32 + 1)]) - .exec() - .await - .unwrap(); - } - Err(e) => { - println!("Error running migration: {}", name); - println!("{}", e); - break; - } - } - } - - #[cfg(debug_assertions)] - println!("Migration {} recorded successfully", name); - } + .await?; } } - Err(err) => { - panic!("Failed to check migration table existence: {:?}", err); - } } - Ok(()) + Ok(client) } diff --git a/packages/client/package.json b/packages/client/package.json index 03ee06fc3..db68bbf13 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -13,25 +13,27 @@ "lint": "TIMING=1 eslint src --fix", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" }, - "devDependencies": { - "@types/react": "^18.0.9", - "scripts": "*", - "tsconfig": "*", - "typescript": "^4.7.2" - }, "jest": { "preset": "scripts/jest/node" }, "dependencies": { "@sd/config": "workspace:*", "@sd/core": "workspace:*", + "@sd/interface": "workspace:*", "eventemitter3": "^4.0.7", "immer": "^9.0.14", - "react-query": "^3.39.1", + "lodash": "^4.17.21", + "react-query": "^3.34.19", "zustand": "4.0.0-rc.1" }, + "devDependencies": { + "@types/react": "^18.0.9", + "scripts": "*", + "tsconfig": "*", + "typescript": "^4.7.2", + "@types/lodash": "^4.14.182" + }, "peerDependencies": { - "react": "^18.0.0", - "react-query": "^3.34.19" + "react": "^18.0.0" } } diff --git a/packages/client/src/bridge.ts b/packages/client/src/bridge.ts index 712cbddde..41e430c66 100644 --- a/packages/client/src/bridge.ts +++ b/packages/client/src/bridge.ts @@ -1,12 +1,8 @@ -import { ClientCommand, ClientQuery, CoreResponse } from '@sd/core'; +import { ClientCommand, ClientQuery, CoreResponse, LibraryCommand, LibraryQuery } from '@sd/core'; import { EventEmitter } from 'eventemitter3'; -import { - UseMutationOptions, - UseQueryOptions, - UseQueryResult, - useMutation, - useQuery -} from 'react-query'; +import { UseMutationOptions, UseQueryOptions, useMutation, useQuery } from 'react-query'; + +import { useLibraryStore } from './stores'; // global var to store the transport TODO: not global :D export let transport: BaseTransport | null = null; @@ -23,11 +19,15 @@ export function setTransport(_transport: BaseTransport) { // extract keys from generated Rust query/command types type QueryKeyType = ClientQuery['key']; +type LibraryQueryKeyType = LibraryQuery['key']; type CommandKeyType = ClientCommand['key']; +type LibraryCommandKeyType = LibraryCommand['key']; // extract the type from the union type CQType = Extract; +type LQType = Extract; type CCType = Extract; +type LCType = Extract; type CRType = Extract; // extract payload type @@ -35,20 +35,18 @@ type ExtractParams

= P extends { params: any } ? P['params'] : never; type ExtractData = D extends { data: any } ? D['data'] : never; // vanilla method to call the transport -export async function queryBridge< - K extends QueryKeyType, - CQ extends CQType, - CR extends CRType ->(key: K, params?: ExtractParams): Promise> { +async function queryBridge, CR extends CRType>( + key: K, + params?: ExtractParams +): Promise> { const result = (await transport?.query({ key, params } as any)) as any; return result?.data; } -export async function commandBridge< - K extends CommandKeyType, - CC extends CCType, - CR extends CRType ->(key: K, params?: ExtractParams): Promise> { +async function commandBridge, CR extends CRType>( + key: K, + params?: ExtractParams +): Promise> { const result = (await transport?.command({ key, params } as any)) as any; return result?.data; } @@ -66,6 +64,21 @@ export function useBridgeQuery, CR ); } +export function useLibraryQuery< + K extends LibraryQueryKeyType, + CQ extends LQType, + CR extends CRType +>(key: K, params?: ExtractParams, options: UseQueryOptions> = {}) { + const library_id = useLibraryStore((state) => state.currentLibraryUuid); + if (!library_id) throw new Error(`Attempted to do library query '${key}' with no library set!`); + + return useQuery>( + [library_id, key, params], + async () => await queryBridge('LibraryQuery', { library_id, query: { key, params } as any }), + options + ); +} + export function useBridgeCommand< K extends CommandKeyType, CC extends CCType, @@ -78,9 +91,35 @@ export function useBridgeCommand< ); } +export function useLibraryCommand< + K extends LibraryCommandKeyType, + LC extends LCType, + CR extends CRType +>(key: K, options: UseMutationOptions> = {}) { + const library_id = useLibraryStore((state) => state.currentLibraryUuid); + if (!library_id) throw new Error(`Attempted to do library command '${key}' with no library set!`); + + return useMutation, unknown, ExtractParams>( + [library_id, key], + async (vars?: ExtractParams) => + await commandBridge('LibraryCommand', { library_id, command: { key, params: vars } as any }), + options + ); +} + export function command, CR extends CRType>( key: K, vars: ExtractParams ): Promise> { return commandBridge(key, vars); } + +export function libraryCommand< + K extends LibraryCommandKeyType, + LC extends LCType, + CR extends CRType +>(key: K, vars: ExtractParams): Promise> { + const library_id = useLibraryStore((state) => state.currentLibraryUuid); + if (!library_id) throw new Error(`Attempted to do library command '${key}' with no library set!`); + return commandBridge('LibraryCommand', { library_id, command: { key, params: vars } as any }); +} diff --git a/packages/interface/src/AppPropsContext.tsx b/packages/client/src/context/AppPropsContext.tsx similarity index 100% rename from packages/interface/src/AppPropsContext.tsx rename to packages/client/src/context/AppPropsContext.tsx diff --git a/packages/client/src/context/index.ts b/packages/client/src/context/index.ts new file mode 100644 index 000000000..70d70d64e --- /dev/null +++ b/packages/client/src/context/index.ts @@ -0,0 +1 @@ +export * from './AppPropsContext'; diff --git a/packages/client/src/files/index.ts b/packages/client/src/files/index.ts deleted file mode 100644 index 1b09d522d..000000000 --- a/packages/client/src/files/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './query'; -export * from './state'; diff --git a/packages/client/src/files/query.ts b/packages/client/src/files/query.ts deleted file mode 100644 index a8ce691f0..000000000 --- a/packages/client/src/files/query.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useState } from 'react'; -import { useQuery } from 'react-query'; - -import { useBridgeCommand, useBridgeQuery } from '../bridge'; -import { useFileExplorerState } from './state'; - -// this hook initializes the explorer state and queries the core -export function useFileExplorer(initialPath = '/', initialLocation: number | null = null) { - const fileState = useFileExplorerState(); - // file explorer hooks maintain their own local state relative to exploration - const [path, setPath] = useState(initialPath); - const [locationId, setLocationId] = useState(initialPath); - - // const { data: volumes } = useQuery(['sys_get_volumes'], () => bridge('sys_get_volumes')); - - return { setPath, setLocationId }; -} - -// export function useVolumes() { -// return useQuery(['SysGetVolumes'], () => bridge('SysGetVolumes')); -// } diff --git a/packages/client/src/files/state.ts b/packages/client/src/files/state.ts deleted file mode 100644 index d817daee4..000000000 --- a/packages/client/src/files/state.ts +++ /dev/null @@ -1,23 +0,0 @@ -import produce from 'immer'; -import create from 'zustand'; - -export interface FileExplorerState { - current_location_id: number | null; - row_limit: number; -} - -interface FileExplorerStore extends FileExplorerState { - update_row_limit: (new_limit: number) => void; -} - -export const useFileExplorerState = create((set, get) => ({ - current_location_id: null, - row_limit: 10, - update_row_limit: (new_limit: number) => { - set((store) => - produce(store, (draft) => { - draft.row_limit = new_limit; - }) - ); - } -})); diff --git a/packages/client/src/hooks/index.ts b/packages/client/src/hooks/index.ts new file mode 100644 index 000000000..25c15a805 --- /dev/null +++ b/packages/client/src/hooks/index.ts @@ -0,0 +1 @@ +export * from './useCoreEvents'; diff --git a/packages/client/src/hooks/useCoreEvents.tsx b/packages/client/src/hooks/useCoreEvents.tsx new file mode 100644 index 000000000..178153c2c --- /dev/null +++ b/packages/client/src/hooks/useCoreEvents.tsx @@ -0,0 +1,59 @@ +import { CoreEvent } from '@sd/core'; +import { useContext, useEffect } from 'react'; +import { useQueryClient } from 'react-query'; + +import { transport, useExplorerStore } from '..'; + +export function useCoreEvents() { + const client = useQueryClient(); + + const { addNewThumbnail } = useExplorerStore(); + useEffect(() => { + function handleCoreEvent(e: CoreEvent) { + switch (e?.key) { + case 'NewThumbnail': + addNewThumbnail(e.data.cas_id); + break; + case 'InvalidateQuery': + case 'InvalidateQueryDebounced': + let query = []; + if (e.data.key === 'LibraryQuery') { + query = [e.data.params.library_id, e.data.params.query.key]; + + // TODO: find a way to make params accessible in TS + // also this method will only work for queries that use the whole params obj as the second key + // @ts-expect-error + if (e.data.params.query.params) { + // @ts-expect-error + query.push(e.data.params.query.params); + } + } else { + query = [e.data.key]; + + // TODO: find a way to make params accessible in TS + // also this method will only work for queries that use the whole params obj as the second key + // @ts-expect-error + if (e.data.params) { + // @ts-expect-error + query.push(e.data.params); + } + } + + client.invalidateQueries(query); + break; + + default: + break; + } + } + // check Tauri Event type + transport?.on('core_event', handleCoreEvent); + + return () => { + transport?.off('core_event', handleCoreEvent); + }; + + // listen('core_event', (e: { payload: CoreEvent }) => { + // }); + }, [transport]); +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 75d5861c4..673524d79 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,3 +1,5 @@ export * from './bridge'; -export * from './files'; export * from './ClientProvider'; +export * from './stores'; +export * from './hooks'; +export * from './context'; diff --git a/packages/client/src/stores/index.ts b/packages/client/src/stores/index.ts new file mode 100644 index 000000000..c38ebef11 --- /dev/null +++ b/packages/client/src/stores/index.ts @@ -0,0 +1,4 @@ +export * from './useLibraryStore'; +export * from './useExplorerStore'; +export * from './useInspectorStore'; +export * from './useInspectorStore'; diff --git a/packages/interface/src/hooks/useExplorerState.ts b/packages/client/src/stores/useExplorerStore.ts similarity index 81% rename from packages/interface/src/hooks/useExplorerState.ts rename to packages/client/src/stores/useExplorerStore.ts index ef2a5ce17..185d88dd1 100644 --- a/packages/interface/src/hooks/useExplorerState.ts +++ b/packages/client/src/stores/useExplorerStore.ts @@ -1,15 +1,16 @@ import create from 'zustand'; -type ExplorerState = { +type ExplorerStore = { selectedRowIndex: number; setSelectedRowIndex: (index: number) => void; locationId: number; setLocationId: (index: number) => void; newThumbnails: Record; addNewThumbnail: (cas_id: string) => void; + reset: () => void; }; -export const useExplorerState = create((set) => ({ +export const useExplorerStore = create((set) => ({ selectedRowIndex: 1, setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })), locationId: -1, @@ -19,5 +20,6 @@ export const useExplorerState = create((set) => ({ set((state) => ({ ...state, newThumbnails: { ...state.newThumbnails, [cas_id]: true } - })) + })), + reset: () => set(() => ({})) })); diff --git a/packages/interface/src/hooks/useInspectorState.tsx b/packages/client/src/stores/useInspectorStore.ts similarity index 82% rename from packages/interface/src/hooks/useInspectorState.tsx rename to packages/client/src/stores/useInspectorStore.ts index 7a7450645..97e9f29b8 100644 --- a/packages/interface/src/hooks/useInspectorState.tsx +++ b/packages/client/src/stores/useInspectorStore.ts @@ -1,17 +1,18 @@ -import { command } from '@sd/client'; import produce from 'immer'; import { debounce } from 'lodash'; import create from 'zustand'; +import { libraryCommand } from '../bridge'; + export type UpdateNoteFN = (vars: { id: number; note: string }) => void; -interface UseInspectorState { +interface InspectorStore { notes: Record; setNote: (file_id: number, note: string) => void; unCacheNote: (file_id: number) => void; } -export const useInspectorState = create((set) => ({ +export const useInspectorStore = create((set) => ({ notes: {}, // set the note locally setNote: (file_id, note) => { @@ -35,7 +36,7 @@ export const useInspectorState = create((set) => ({ // direct command call to update note export const updateNote = debounce(async (file_id: number, note: string) => { - return await command('FileSetNote', { + return await libraryCommand('FileSetNote', { id: file_id, note }); diff --git a/packages/client/src/stores/useLibraryStore.ts b/packages/client/src/stores/useLibraryStore.ts new file mode 100644 index 000000000..53a8a4e55 --- /dev/null +++ b/packages/client/src/stores/useLibraryStore.ts @@ -0,0 +1,67 @@ +import { LibraryConfigWrapped } from '@sd/core'; +import produce from 'immer'; +import { useMemo } from 'react'; +import { useQueryClient } from 'react-query'; +import create from 'zustand'; +import { devtools, persist } from 'zustand/middleware'; + +import { useBridgeQuery } from '../bridge'; +import { useExplorerStore } from './useExplorerStore'; + +type LibraryStore = { + // the uuid of the currently active library + currentLibraryUuid: string | null; + // for full functionality this should be triggered along-side query invalidation + switchLibrary: (uuid: string) => void; + // a function + init: (libraries: LibraryConfigWrapped[]) => Promise; +}; + +export const useLibraryStore = create()( + devtools( + persist( + (set) => ({ + currentLibraryUuid: null, + switchLibrary: (uuid) => { + set((state) => + produce(state, (draft) => { + draft.currentLibraryUuid = uuid; + }) + ); + // reset other stores + useExplorerStore().reset(); + }, + init: async (libraries) => { + set((state) => + produce(state, (draft) => { + // use first library default if none set + if (!state.currentLibraryUuid) { + draft.currentLibraryUuid = libraries[0].uuid; + } + }) + ); + } + }), + { name: 'sd-library-store' } + ) + ) +); + +// this must be used at least once in the app to correct the initial state +// is memorized and can be used safely in any component +export const useCurrentLibrary = () => { + const { currentLibraryUuid, switchLibrary } = useLibraryStore(); + const { data: libraries } = useBridgeQuery('NodeGetLibraries', undefined, {}); + + // memorize library to avoid re-running find function + const currentLibrary = useMemo(() => { + const current = libraries?.find((l) => l.uuid === currentLibraryUuid); + // switch to first library if none set + if (Array.isArray(libraries) && !current && libraries[0]?.uuid) { + switchLibrary(libraries[0]?.uuid); + } + return current; + }, [libraries, currentLibraryUuid]); + + return { currentLibrary, libraries, currentLibraryUuid }; +}; diff --git a/packages/interface/package.json b/packages/interface/package.json index c532d24d4..8577584cd 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -46,7 +46,7 @@ "react-loading-icons": "^1.1.0", "react-loading-skeleton": "^3.1.0", "react-portal": "^4.2.2", - "react-query": "^3.39.1", + "react-query": "^3.34.19", "react-router": "6.3.0", "react-router-dom": "6.3.0", "react-scrollbars-custom": "^4.0.27", @@ -55,6 +55,7 @@ "react-virtuoso": "^2.12.1", "rooks": "^5.11.2", "tailwindcss": "^3.0.24", + "use-debounce": "^8.0.1", "zustand": "4.0.0-rc.1" }, "devDependencies": { diff --git a/packages/interface/src/App.tsx b/packages/interface/src/App.tsx index 339ff96fd..514d60a36 100644 --- a/packages/interface/src/App.tsx +++ b/packages/interface/src/App.tsx @@ -1,14 +1,14 @@ import '@fontsource/inter/variable.css'; import { BaseTransport, ClientProvider, setTransport } from '@sd/client'; +import { useCoreEvents } from '@sd/client'; +import { AppProps, AppPropsContext } from '@sd/client'; import React from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { QueryClient, QueryClientProvider } from 'react-query'; import { MemoryRouter } from 'react-router-dom'; -import { AppProps, AppPropsContext } from './AppPropsContext'; import { AppRouter } from './AppRouter'; import { ErrorFallback } from './ErrorFallback'; -import { useCoreEvents } from './hooks/useCoreEvents'; import './style.scss'; const queryClient = new QueryClient(); diff --git a/packages/interface/src/AppLayout.tsx b/packages/interface/src/AppLayout.tsx index 7b1b2dfc5..0c3ecbb11 100644 --- a/packages/interface/src/AppLayout.tsx +++ b/packages/interface/src/AppLayout.tsx @@ -1,8 +1,8 @@ +import { AppPropsContext } from '@sd/client'; import clsx from 'clsx'; import React, { useContext } from 'react'; import { Outlet } from 'react-router-dom'; -import { AppPropsContext } from './AppPropsContext'; import { Sidebar } from './components/file/Sidebar'; export function AppLayout() { diff --git a/packages/interface/src/AppRouter.tsx b/packages/interface/src/AppRouter.tsx index 2c3bbfde1..db0ffdacd 100644 --- a/packages/interface/src/AppRouter.tsx +++ b/packages/interface/src/AppRouter.tsx @@ -1,3 +1,5 @@ +import { useBridgeQuery } from '@sd/client'; +import { useLibraryStore } from '@sd/client'; import React, { useEffect } from 'react'; import { Route, Routes, useLocation } from 'react-router-dom'; @@ -9,56 +11,81 @@ import { ExplorerScreen } from './screens/Explorer'; import { OverviewScreen } from './screens/Overview'; import { PhotosScreen } from './screens/Photos'; import { RedirectPage } from './screens/Redirect'; -import { SettingsScreen } from './screens/Settings'; import { TagScreen } from './screens/Tag'; -import AppearanceSettings from './screens/settings/AppearanceSettings'; -import ContactsSettings from './screens/settings/ContactsSettings'; -import ExperimentalSettings from './screens/settings/ExperimentalSettings'; -import GeneralSettings from './screens/settings/GeneralSettings'; -import KeysSettings from './screens/settings/KeysSetting'; -import LibrarySettings from './screens/settings/LibrarySettings'; -import LocationSettings from './screens/settings/LocationSettings'; -import SecuritySettings from './screens/settings/SecuritySettings'; -import SharingSettings from './screens/settings/SharingSettings'; -import SyncSettings from './screens/settings/SyncSettings'; -import TagsSettings from './screens/settings/TagsSettings'; +import { CurrentLibrarySettings } from './screens/settings/CurrentLibrarySettings'; +import { SettingsScreen } from './screens/settings/Settings'; +import AppearanceSettings from './screens/settings/client/AppearanceSettings'; +import GeneralSettings from './screens/settings/client/GeneralSettings'; +import ContactsSettings from './screens/settings/library/ContactsSettings'; +import KeysSettings from './screens/settings/library/KeysSetting'; +import LibraryGeneralSettings from './screens/settings/library/LibraryGeneralSettings'; +import LocationSettings from './screens/settings/library/LocationSettings'; +import SecuritySettings from './screens/settings/library/SecuritySettings'; +import SharingSettings from './screens/settings/library/SharingSettings'; +import SyncSettings from './screens/settings/library/SyncSettings'; +import TagsSettings from './screens/settings/library/TagsSettings'; +import ExperimentalSettings from './screens/settings/node/ExperimentalSettings'; +import LibrarySettings from './screens/settings/node/LibrariesSettings'; +import NodesSettings from './screens/settings/node/NodesSettings'; +import P2PSettings from './screens/settings/node/P2PSettings'; export function AppRouter() { let location = useLocation(); let state = location.state as { backgroundLocation?: Location }; + const libraryState = useLibraryStore(); + const { data: libraries } = useBridgeQuery('NodeGetLibraries'); + // TODO: This can be removed once we add a setup flow to the app useEffect(() => { - console.log({ url: location.pathname }); - }, [state]); + if (libraryState.currentLibraryUuid === null && libraries && libraries.length > 0) { + libraryState.switchLibrary(libraries[0].uuid); + } + }, [libraryState.currentLibraryUuid, libraries]); return ( <> - - }> - } /> - } /> - } /> - } /> - } /> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + {libraryState.currentLibraryUuid === null ? ( + <> + {/* TODO: Remove this when adding app setup flow */} +

No Library Loaded...

+ + ) : ( + + }> + } /> + } /> + } /> + } /> + } /> + }> + } /> + } /> + } /> + } /> + } /> + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> - } /> - } /> - } /> - - + + )} ); } diff --git a/packages/interface/src/NotFound.tsx b/packages/interface/src/NotFound.tsx index c48e8141e..ae4acef1b 100644 --- a/packages/interface/src/NotFound.tsx +++ b/packages/interface/src/NotFound.tsx @@ -10,7 +10,7 @@ export function NotFound() { role="alert" className="flex flex-col items-center justify-center w-full h-full p-4 rounded-lg dark:text-white" > -

Error: 404

+

Error: 404

You chose nothingness.

+ {(locations?.length || 0) < 1 && ( + + )}
Tags diff --git a/packages/interface/src/components/layout/Card.tsx b/packages/interface/src/components/layout/Card.tsx new file mode 100644 index 000000000..6059bf0c0 --- /dev/null +++ b/packages/interface/src/components/layout/Card.tsx @@ -0,0 +1,15 @@ +import clsx from 'clsx'; +import React, { ReactNode } from 'react'; + +export default function Card(props: { children: ReactNode; className?: string }) { + return ( +
+ {props.children} +
+ ); +} diff --git a/packages/interface/src/components/layout/Dialog.tsx b/packages/interface/src/components/layout/Dialog.tsx index 3a30992ab..1c32ba432 100644 --- a/packages/interface/src/components/layout/Dialog.tsx +++ b/packages/interface/src/components/layout/Dialog.tsx @@ -5,7 +5,7 @@ import React, { ReactNode } from 'react'; import Loader from '../primitive/Loader'; -export interface DialogProps { +export interface DialogProps extends DialogPrimitive.DialogProps { trigger: ReactNode; ctaLabel?: string; ctaDanger?: boolean; @@ -18,13 +18,15 @@ export interface DialogProps { export default function Dialog(props: DialogProps) { return ( - + {props.trigger}
- {props.title} + + {props.title} + {props.description} diff --git a/packages/interface/src/components/layout/TopBar.tsx b/packages/interface/src/components/layout/TopBar.tsx index a451dc7f1..828666a01 100644 --- a/packages/interface/src/components/layout/TopBar.tsx +++ b/packages/interface/src/components/layout/TopBar.tsx @@ -1,5 +1,6 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline'; -import { useBridgeCommand } from '@sd/client'; +import { useLibraryCommand } from '@sd/client'; +import { useExplorerStore } from '@sd/client'; import { Dropdown } from '@sd/ui'; import clsx from 'clsx'; import { @@ -15,7 +16,6 @@ import { import React, { DetailedHTMLProps, HTMLAttributes } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useExplorerState } from '../../hooks/useExplorerState'; import { Shortcut } from '../primitive/Shortcut'; import { DefaultProps } from '../primitive/types'; @@ -50,14 +50,14 @@ const TopBarButton: React.FC = ({ icon: Icon, ...props }) => }; export const TopBar: React.FC = (props) => { - const { locationId } = useExplorerState(); - const { mutate: generateThumbsForLocation } = useBridgeCommand('GenerateThumbsForLocation', { + const { locationId } = useExplorerStore(); + const { mutate: generateThumbsForLocation } = useLibraryCommand('GenerateThumbsForLocation', { onMutate: (data) => { console.log('GenerateThumbsForLocation', data); } }); - const { mutate: identifyUniqueFiles } = useBridgeCommand('IdentifyUniqueFiles', { + const { mutate: identifyUniqueFiles } = useLibraryCommand('IdentifyUniqueFiles', { onMutate: (data) => { console.log('IdentifyUniqueFiles', data); }, diff --git a/packages/interface/src/components/location/LocationListItem.tsx b/packages/interface/src/components/location/LocationListItem.tsx index 7b20e759c..3ccff9eb4 100644 --- a/packages/interface/src/components/location/LocationListItem.tsx +++ b/packages/interface/src/components/location/LocationListItem.tsx @@ -1,6 +1,6 @@ import { DotsVerticalIcon, RefreshIcon } from '@heroicons/react/outline'; -import { CogIcon, TrashIcon } from '@heroicons/react/solid'; -import { command, useBridgeCommand } from '@sd/client'; +import { TrashIcon } from '@heroicons/react/solid'; +import { useLibraryCommand } from '@sd/client'; import { LocationResource } from '@sd/core'; import { Button } from '@sd/ui'; import clsx from 'clsx'; @@ -16,9 +16,9 @@ interface LocationListItemProps { export default function LocationListItem({ location }: LocationListItemProps) { const [hide, setHide] = useState(false); - const { mutate: locRescan } = useBridgeCommand('LocRescan'); + const { mutate: locRescan } = useLibraryCommand('LocRescan'); - const { mutate: deleteLoc, isLoading: locDeletePending } = useBridgeCommand('LocDelete', { + const { mutate: deleteLoc, isLoading: locDeletePending } = useLibraryCommand('LocDelete', { onSuccess: () => { setHide(true); } diff --git a/packages/interface/src/components/settings/SettingsContainer.tsx b/packages/interface/src/components/settings/SettingsContainer.tsx index bcd448ab3..a5e313680 100644 --- a/packages/interface/src/components/settings/SettingsContainer.tsx +++ b/packages/interface/src/components/settings/SettingsContainer.tsx @@ -5,5 +5,5 @@ interface SettingsContainerProps { } export const SettingsContainer: React.FC = (props) => { - return
{props.children}
; + return
{props.children}
; }; diff --git a/packages/interface/src/components/settings/SettingsHeader.tsx b/packages/interface/src/components/settings/SettingsHeader.tsx index 633fa0328..f64df584b 100644 --- a/packages/interface/src/components/settings/SettingsHeader.tsx +++ b/packages/interface/src/components/settings/SettingsHeader.tsx @@ -1,15 +1,19 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; interface SettingsHeaderProps { title: string; description: string; + rightArea?: ReactNode; } export const SettingsHeader: React.FC = (props) => { return ( -
-

{props.title}

-

{props.description}

+
+
+

{props.title}

+

{props.description}

+
+ {props.rightArea}
); diff --git a/packages/interface/src/components/settings/SettingsScreenContainer.tsx b/packages/interface/src/components/settings/SettingsScreenContainer.tsx new file mode 100644 index 000000000..fd0363a59 --- /dev/null +++ b/packages/interface/src/components/settings/SettingsScreenContainer.tsx @@ -0,0 +1,40 @@ +import clsx from 'clsx'; +import React from 'react'; +import { Outlet } from 'react-router'; + +interface SettingsScreenContainerProps { + children: React.ReactNode; +} + +export const SettingsIcon = ({ component: Icon, ...props }: any) => ( + +); + +export const SettingsHeading: React.FC<{ className?: string; children: string }> = ({ + children, + className +}) => ( +
+ {children} +
+); + +export const SettingsScreenContainer: React.FC = (props) => { + return ( +
+
+
+
{props.children}
+
+
+
+
+
+ +
+
+
+
+
+ ); +}; diff --git a/packages/interface/src/hooks/useCoreEvents.tsx b/packages/interface/src/hooks/useCoreEvents.tsx deleted file mode 100644 index 729068f73..000000000 --- a/packages/interface/src/hooks/useCoreEvents.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { transport } from '@sd/client'; -import { CoreEvent } from '@sd/core'; -import { useContext, useEffect } from 'react'; -import { useQueryClient } from 'react-query'; - -import { AppPropsContext } from '../AppPropsContext'; -import { useExplorerState } from './useExplorerState'; - -export function useCoreEvents() { - const client = useQueryClient(); - - const { addNewThumbnail } = useExplorerState(); - useEffect(() => { - function handleCoreEvent(e: CoreEvent) { - switch (e?.key) { - case 'NewThumbnail': - addNewThumbnail(e.data.cas_id); - break; - case 'InvalidateQuery': - case 'InvalidateQueryDebounced': - let query = [e.data.key]; - // TODO: find a way to make params accessible in TS - // also this method will only work for queries that use the whole params obj as the second key - // @ts-expect-error - if (e.data.params) { - // @ts-expect-error - query.push(e.data.params); - } - client.invalidateQueries(e.data.key); - break; - - default: - break; - } - } - // check Tauri Event type - transport?.on('core_event', handleCoreEvent); - - return () => { - transport?.off('core_event', handleCoreEvent); - }; - - // listen('core_event', (e: { payload: CoreEvent }) => { - // }); - }, [transport]); -} diff --git a/packages/interface/src/index.ts b/packages/interface/src/index.ts index eefb2853f..896dd051e 100644 --- a/packages/interface/src/index.ts +++ b/packages/interface/src/index.ts @@ -1,5 +1,6 @@ +import { AppProps, Platform } from '@sd/client'; + import App from './App'; -import { AppProps, Platform } from './AppPropsContext'; export type { AppProps, Platform }; diff --git a/packages/interface/src/screens/Debug.tsx b/packages/interface/src/screens/Debug.tsx index b5788c203..d0758facb 100644 --- a/packages/interface/src/screens/Debug.tsx +++ b/packages/interface/src/screens/Debug.tsx @@ -1,21 +1,22 @@ -import { useBridgeCommand, useBridgeQuery } from '@sd/client'; +import { useBridgeQuery, useLibraryCommand, useLibraryQuery } from '@sd/client'; +import { AppPropsContext } from '@sd/client'; import { Button } from '@sd/ui'; import React, { useContext } from 'react'; -import { AppPropsContext } from '../AppPropsContext'; import CodeBlock from '../components/primitive/Codeblock'; export const DebugScreen: React.FC<{}> = (props) => { const appPropsContext = useContext(AppPropsContext); - const { data: client } = useBridgeQuery('NodeGetState'); + const { data: nodeState } = useBridgeQuery('NodeGetState'); + const { data: libraryState } = useBridgeQuery('NodeGetLibraries'); const { data: jobs } = useBridgeQuery('JobGetRunning'); - const { data: jobHistory } = useBridgeQuery('JobGetHistory'); + const { data: jobHistory } = useLibraryQuery('JobGetHistory'); // const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', { // onMutate: () => { // alert('Database purged'); // } // }); - const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles'); + const { mutate: identifyFiles } = useLibraryCommand('IdentifyUniqueFiles'); return (
@@ -27,8 +28,8 @@ export const DebugScreen: React.FC<{}> = (props) => { variant="gray" size="sm" onClick={() => { - if (client && appPropsContext?.onOpen) { - appPropsContext.onOpen(client.data_path); + if (nodeState && appPropsContext?.onOpen) { + appPropsContext.onOpen(nodeState.data_path); } }} > @@ -39,8 +40,10 @@ export const DebugScreen: React.FC<{}> = (props) => {

Job History

-

Client State

- +

Node State

+ +

Libraries

+
); diff --git a/packages/interface/src/screens/Explorer.tsx b/packages/interface/src/screens/Explorer.tsx index 742339eab..4c1df35ec 100644 --- a/packages/interface/src/screens/Explorer.tsx +++ b/packages/interface/src/screens/Explorer.tsx @@ -1,11 +1,11 @@ -import { useBridgeQuery } from '@sd/client'; +import { useLibraryQuery } from '@sd/client'; +import { useExplorerStore } from '@sd/client'; import React from 'react'; import { useParams, useSearchParams } from 'react-router-dom'; import { FileList } from '../components/file/FileList'; import { Inspector } from '../components/file/Inspector'; import { TopBar } from '../components/layout/TopBar'; -import { useExplorerState } from '../hooks/useExplorerState'; export const ExplorerScreen: React.FC<{}> = () => { let [searchParams] = useSearchParams(); @@ -16,13 +16,13 @@ export const ExplorerScreen: React.FC<{}> = () => { const [limit, setLimit] = React.useState(100); - const { selectedRowIndex } = useExplorerState(); + const { selectedRowIndex } = useExplorerStore(); // Current Location - const { data: currentLocation } = useBridgeQuery('SysGetLocation', { id: location_id }); + const { data: currentLocation } = useLibraryQuery('SysGetLocation', { id: location_id }); // Current Directory - const { data: currentDir } = useBridgeQuery( + const { data: currentDir } = useLibraryQuery( 'LibGetExplorerDir', { location_id: location_id!, path, limit }, { enabled: !!location_id } diff --git a/packages/interface/src/screens/Overview.tsx b/packages/interface/src/screens/Overview.tsx index 38617eb0d..2b0cd4d13 100644 --- a/packages/interface/src/screens/Overview.tsx +++ b/packages/interface/src/screens/Overview.tsx @@ -1,5 +1,6 @@ -import { PlusIcon } from '@heroicons/react/solid'; -import { useBridgeQuery } from '@sd/client'; +import { DatabaseIcon, ExclamationCircleIcon, PlusIcon } from '@heroicons/react/solid'; +import { useBridgeQuery, useLibraryQuery } from '@sd/client'; +import { AppPropsContext } from '@sd/client'; import { Statistics } from '@sd/core'; import { Button, Input } from '@sd/ui'; import byteSize from 'byte-size'; @@ -10,7 +11,6 @@ import Skeleton from 'react-loading-skeleton'; import 'react-loading-skeleton/dist/skeleton.css'; import create from 'zustand'; -import { AppPropsContext } from '../AppPropsContext'; import { Device } from '../components/device/Device'; import Dialog from '../components/layout/Dialog'; @@ -102,7 +102,7 @@ const StatItem: React.FC = (props) => { export const OverviewScreen = () => { const { data: libraryStatistics, isLoading: isStatisticsLoading } = - useBridgeQuery('GetLibraryStatistics'); + useLibraryQuery('GetLibraryStatistics'); const { data: nodeState } = useBridgeQuery('NodeGetState'); const { overviewStats, setOverviewStats } = useOverviewState(); @@ -157,7 +157,17 @@ export const OverviewScreen = () => { {/* STAT HEADER */}
{/* STAT CONTAINER */} -
+
+ {!libraryStatistics && ( +
+
+ Missing library +
+ + Ensure the library you have loaded still exists on disk + +
+ )} {Object.entries(overviewStats).map(([key, value]) => { if (!displayableStatItems.includes(key)) return null; @@ -171,8 +181,9 @@ export const OverviewScreen = () => { ); })}
+
-
+
{
-
+
( - -); - -const Heading: React.FC<{ className?: string; children: string }> = ({ children, className }) => ( -
- {children} -
-); - -export const SettingsScreen: React.FC<{}> = () => { - return ( -
-
-
-
- Client - - - General - - - - Security - - - - Appearance - - - - Experimental - - - Library - - - Database - - - - Locations - - - - - Keys - - - - Tags - - - Cloud - - - Sync - - - - Contacts - -
-
-
-
-
-
- -
-
-
-
-
- ); -}; diff --git a/packages/interface/src/screens/settings/CurrentLibrarySettings.tsx b/packages/interface/src/screens/settings/CurrentLibrarySettings.tsx new file mode 100644 index 000000000..ec8426952 --- /dev/null +++ b/packages/interface/src/screens/settings/CurrentLibrarySettings.tsx @@ -0,0 +1,42 @@ +import { CogIcon, DatabaseIcon, KeyIcon, TagIcon } from '@heroicons/react/outline'; +import { HardDrive, ShareNetwork } from 'phosphor-react'; +import React from 'react'; + +import { SidebarLink } from '../../components/file/Sidebar'; +import { + SettingsHeading, + SettingsIcon, + SettingsScreenContainer +} from '../../components/settings/SettingsScreenContainer'; + +export const CurrentLibrarySettings: React.FC = () => { + return ( + + Library Settings + + + General + + + + Locations + + + + Tags + + + + Keys + + + + Backups + + + + Sync + + + ); +}; diff --git a/packages/interface/src/screens/settings/GeneralSettings.tsx b/packages/interface/src/screens/settings/GeneralSettings.tsx deleted file mode 100644 index e908b3789..000000000 --- a/packages/interface/src/screens/settings/GeneralSettings.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useBridgeQuery } from '@sd/client'; -import React from 'react'; - -import { InputContainer } from '../../components/primitive/InputContainer'; -import Listbox from '../../components/primitive/Listbox'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; - -export default function GeneralSettings() { - const { data: volumes } = useBridgeQuery('SysGetVolumes'); - - return ( - - - -
-
- { - const name = volume.name && volume.name.length ? volume.name : volume.mount_point; - return { - key: name, - option: name, - description: volume.mount_point - }; - }) ?? [] - } - /> -
-
-
- - {/*
{JSON.stringify({ config })}
*/} -
- ); -} diff --git a/packages/interface/src/screens/settings/LibrarySettings.tsx b/packages/interface/src/screens/settings/LibrarySettings.tsx deleted file mode 100644 index 9b54f8725..000000000 --- a/packages/interface/src/screens/settings/LibrarySettings.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; - -import { Toggle } from '../../components/primitive'; -import { InputContainer } from '../../components/primitive/InputContainer'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; - -// type LibrarySecurity = 'public' | 'password' | 'vault'; - -export default function LibrarySettings() { - // const locations = useBridgeQuery("SysGetLocation") - const [encryptOnCloud, setEncryptOnCloud] = React.useState(false); - - return ( - - {/* */} - - -
- -
-
-
- ); -} diff --git a/packages/interface/src/screens/settings/SecuritySettings.tsx b/packages/interface/src/screens/settings/SecuritySettings.tsx deleted file mode 100644 index e8b39dec3..000000000 --- a/packages/interface/src/screens/settings/SecuritySettings.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Button } from '@sd/ui'; -import React from 'react'; - -import { InputContainer } from '../../components/primitive/InputContainer'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; - -export default function SecuritySettings() { - return ( - - - -
- - {/**/} -
-
-
- ); -} diff --git a/packages/interface/src/screens/settings/Settings.tsx b/packages/interface/src/screens/settings/Settings.tsx new file mode 100644 index 000000000..fc903d4aa --- /dev/null +++ b/packages/interface/src/screens/settings/Settings.tsx @@ -0,0 +1,83 @@ +import { + CogIcon, + CollectionIcon, + GlobeAltIcon, + KeyIcon, + TerminalIcon +} from '@heroicons/react/outline'; +import { HardDrive, PaintBrush, ShareNetwork } from 'phosphor-react'; +import React from 'react'; + +import { SidebarLink } from '../../components/file/Sidebar'; +import { + SettingsHeading, + SettingsIcon, + SettingsScreenContainer +} from '../../components/settings/SettingsScreenContainer'; + +export const SettingsScreen: React.FC = () => { + return ( + + Client + + + General + + + + Appearance + + + Node + + + Nodes + + + + P2P + + + + Libraries + + + + Security + + Developer + + + Experimental + + {/* Library + + + My Libraries + + + + Locations + + + + + Keys + + + + Tags + */} + + {/* Cloud + + + Sync + + + + Contacts + */} + + ); +}; diff --git a/packages/interface/src/screens/settings/AppearanceSettings.tsx b/packages/interface/src/screens/settings/client/AppearanceSettings.tsx similarity index 58% rename from packages/interface/src/screens/settings/AppearanceSettings.tsx rename to packages/interface/src/screens/settings/client/AppearanceSettings.tsx index 746d3d273..177b5b1a4 100644 --- a/packages/interface/src/screens/settings/AppearanceSettings.tsx +++ b/packages/interface/src/screens/settings/client/AppearanceSettings.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function AppearanceSettings() { return ( diff --git a/packages/interface/src/screens/settings/client/GeneralSettings.tsx b/packages/interface/src/screens/settings/client/GeneralSettings.tsx new file mode 100644 index 000000000..66550f7c8 --- /dev/null +++ b/packages/interface/src/screens/settings/client/GeneralSettings.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function GeneralSettings() { + // const { data: volumes } = useBridgeQuery('SysGetVolumes'); + + return ( + + + {/* +
+
+ { + const name = volume.name && volume.name.length ? volume.name : volume.mount_point; + return { + key: name, + option: name, + description: volume.mount_point + }; + }) ?? [] + } + /> +
+
+
*/} +
+ ); +} diff --git a/packages/interface/src/screens/settings/ContactsSettings.tsx b/packages/interface/src/screens/settings/library/ContactsSettings.tsx similarity index 58% rename from packages/interface/src/screens/settings/ContactsSettings.tsx rename to packages/interface/src/screens/settings/library/ContactsSettings.tsx index 581c1df18..014a7316d 100644 --- a/packages/interface/src/screens/settings/ContactsSettings.tsx +++ b/packages/interface/src/screens/settings/library/ContactsSettings.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function ContactsSettings() { return ( diff --git a/packages/interface/src/screens/settings/KeysSetting.tsx b/packages/interface/src/screens/settings/library/KeysSetting.tsx similarity index 55% rename from packages/interface/src/screens/settings/KeysSetting.tsx rename to packages/interface/src/screens/settings/library/KeysSetting.tsx index 5e9087fce..388d3fc44 100644 --- a/packages/interface/src/screens/settings/KeysSetting.tsx +++ b/packages/interface/src/screens/settings/library/KeysSetting.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function KeysSettings() { return ( diff --git a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx new file mode 100644 index 000000000..87e70e4e0 --- /dev/null +++ b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx @@ -0,0 +1,91 @@ +import { useBridgeCommand, useBridgeQuery } from '@sd/client'; +import { useCurrentLibrary } from '@sd/client'; +import { Button, Input } from '@sd/ui'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDebounce } from 'use-debounce'; + +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function LibraryGeneralSettings() { + const { currentLibrary, libraries, currentLibraryUuid } = useCurrentLibrary(); + + const { mutate: editLibrary } = useBridgeCommand('EditLibrary'); + + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [encryptLibrary, setEncryptLibrary] = useState(false); + + const [nameDebounced] = useDebounce(name, 500); + const [descriptionDebounced] = useDebounce(description, 500); + + useEffect(() => { + if (currentLibrary) { + const { name, description } = currentLibrary.config; + // currentLibrary must be loaded, name must not be empty, and must be different from the current + if (nameDebounced && (nameDebounced !== name || descriptionDebounced !== description)) { + editLibrary({ + id: currentLibraryUuid!, + name: nameDebounced, + description: descriptionDebounced + }); + } + } + }, [nameDebounced, descriptionDebounced]); + + useEffect(() => { + if (currentLibrary) { + setName(currentLibrary.config.name); + setDescription(currentLibrary.config.description); + } + }, [libraries]); + + return ( + + +
+
+ Name + setName(e.target.value)} + defaultValue="My Default Library" + /> +
+
+ Description + setDescription(e.target.value)} + placeholder="" + /> +
+
+ + +
+ +
+
+ +
+ +
+
+
+ ); +} diff --git a/packages/interface/src/screens/settings/library/LocationSettings.tsx b/packages/interface/src/screens/settings/library/LocationSettings.tsx new file mode 100644 index 000000000..3b6c67c26 --- /dev/null +++ b/packages/interface/src/screens/settings/library/LocationSettings.tsx @@ -0,0 +1,55 @@ +import { PlusIcon } from '@heroicons/react/solid'; +import { useBridgeQuery, useLibraryCommand, useLibraryQuery } from '@sd/client'; +import { AppPropsContext } from '@sd/client'; +import { Button } from '@sd/ui'; +import React, { useContext } from 'react'; + +import LocationListItem from '../../../components/location/LocationListItem'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +// const exampleLocations = [ +// { option: 'Macintosh HD', key: 'macintosh_hd' }, +// { option: 'LaCie External', key: 'lacie_external' }, +// { option: 'Seagate 8TB', key: 'seagate_8tb' } +// ]; + +export default function LocationSettings() { + const { data: locations } = useLibraryQuery('SysGetLocations'); + + const appProps = useContext(AppPropsContext); + + const { mutate: createLocation } = useLibraryCommand('LocCreate'); + + return ( + + {/**/} + + +
+ } + /> + +
+ {locations?.map((location) => ( + + ))} +
+ + ); +} diff --git a/packages/interface/src/screens/settings/library/SecuritySettings.tsx b/packages/interface/src/screens/settings/library/SecuritySettings.tsx new file mode 100644 index 000000000..ac3e7a87d --- /dev/null +++ b/packages/interface/src/screens/settings/library/SecuritySettings.tsx @@ -0,0 +1,14 @@ +import { Button } from '@sd/ui'; +import React from 'react'; + +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function SecuritySettings() { + return ( + + + + ); +} diff --git a/packages/interface/src/screens/settings/SharingSettings.tsx b/packages/interface/src/screens/settings/library/SharingSettings.tsx similarity index 58% rename from packages/interface/src/screens/settings/SharingSettings.tsx rename to packages/interface/src/screens/settings/library/SharingSettings.tsx index 4403271c1..23ef94e67 100644 --- a/packages/interface/src/screens/settings/SharingSettings.tsx +++ b/packages/interface/src/screens/settings/library/SharingSettings.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function SharingSettings() { return ( diff --git a/packages/interface/src/screens/settings/SyncSettings.tsx b/packages/interface/src/screens/settings/library/SyncSettings.tsx similarity index 56% rename from packages/interface/src/screens/settings/SyncSettings.tsx rename to packages/interface/src/screens/settings/library/SyncSettings.tsx index 73842468d..9cdb85193 100644 --- a/packages/interface/src/screens/settings/SyncSettings.tsx +++ b/packages/interface/src/screens/settings/library/SyncSettings.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function SyncSettings() { return ( diff --git a/packages/interface/src/screens/settings/TagsSettings.tsx b/packages/interface/src/screens/settings/library/TagsSettings.tsx similarity index 55% rename from packages/interface/src/screens/settings/TagsSettings.tsx rename to packages/interface/src/screens/settings/library/TagsSettings.tsx index d1aac3e81..19bb977f6 100644 --- a/packages/interface/src/screens/settings/TagsSettings.tsx +++ b/packages/interface/src/screens/settings/library/TagsSettings.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function TagsSettings() { return ( diff --git a/packages/interface/src/screens/settings/ExperimentalSettings.tsx b/packages/interface/src/screens/settings/node/ExperimentalSettings.tsx similarity index 64% rename from packages/interface/src/screens/settings/ExperimentalSettings.tsx rename to packages/interface/src/screens/settings/node/ExperimentalSettings.tsx index 62a253f8a..c274da7dd 100644 --- a/packages/interface/src/screens/settings/ExperimentalSettings.tsx +++ b/packages/interface/src/screens/settings/node/ExperimentalSettings.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import { useNodeStore } from '../../components/device/Stores'; -import { Toggle } from '../../components/primitive'; -import { InputContainer } from '../../components/primitive/InputContainer'; -import { SettingsContainer } from '../../components/settings/SettingsContainer'; -import { SettingsHeader } from '../../components/settings/SettingsHeader'; +import { useNodeStore } from '../../../components/device/Stores'; +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function ExperimentalSettings() { - // const locations = useBridgeQuery("SysGetLocation") - const { isExperimental, setIsExperimental } = useNodeStore(); return ( diff --git a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx new file mode 100644 index 000000000..3f1b9c3a0 --- /dev/null +++ b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx @@ -0,0 +1,113 @@ +import { CollectionIcon, TrashIcon } from '@heroicons/react/outline'; +import { PlusIcon } from '@heroicons/react/solid'; +import { useBridgeCommand, useBridgeQuery } from '@sd/client'; +import { AppPropsContext } from '@sd/client'; +import { LibraryConfig, LibraryConfigWrapped } from '@sd/core'; +import { Button, Input } from '@sd/ui'; +import React, { useContext, useState } from 'react'; + +import Card from '../../../components/layout/Card'; +import Dialog from '../../../components/layout/Dialog'; +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +// type LibrarySecurity = 'public' | 'password' | 'vault'; + +function LibraryListItem(props: { library: LibraryConfigWrapped }) { + const [openDeleteModal, setOpenDeleteModal] = useState(false); + + const { mutate: deleteLib, isLoading: libDeletePending } = useBridgeCommand('DeleteLibrary', { + onSuccess: () => { + setOpenDeleteModal(false); + } + }); + + return ( + +
+

{props.library.config.name}

+

{props.library.uuid}

+
+
+ { + deleteLib({ id: props.library.uuid }); + }} + loading={libDeletePending} + ctaDanger + ctaLabel="Delete" + trigger={ + + } + /> +
+
+ ); +} + +export default function LibrarySettings() { + const [openCreateModal, setOpenCreateModal] = useState(false); + const [newLibName, setNewLibName] = useState(''); + + const { mutate: createLibrary, isLoading: createLibLoading } = useBridgeCommand('CreateLibrary', { + onSuccess: () => { + setOpenCreateModal(false); + } + }); + + const { data: libraries } = useBridgeQuery('NodeGetLibraries'); + + function createNewLib() { + if (newLibName) { + createLibrary({ name: newLibName }); + } + } + + return ( + + + + Add Library + + } + > + setNewLibName(e.target.value)} + /> + +
+ } + /> + +
+ {libraries?.map((library) => ( + + ))} +
+ + ); +} diff --git a/packages/interface/src/screens/settings/node/NodesSettings.tsx b/packages/interface/src/screens/settings/node/NodesSettings.tsx new file mode 100644 index 000000000..75595f42f --- /dev/null +++ b/packages/interface/src/screens/settings/node/NodesSettings.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function NodesSettings() { + return ( + + + + ); +} diff --git a/packages/interface/src/screens/settings/node/P2PSettings.tsx b/packages/interface/src/screens/settings/node/P2PSettings.tsx new file mode 100644 index 000000000..aee248624 --- /dev/null +++ b/packages/interface/src/screens/settings/node/P2PSettings.tsx @@ -0,0 +1,40 @@ +import { useBridgeQuery } from '@sd/client'; +import { Button, Input } from '@sd/ui'; +import React from 'react'; + +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import Listbox from '../../../components/primitive/Listbox'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function P2PSettings() { + return ( + + + + + + + + +
+ +
+ Change +
+
+
+
+ ); +} diff --git a/packages/ui/package.json b/packages/ui/package.json index ad1cc798d..888cf519d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -17,7 +17,7 @@ "storybook:build": "build-storybook" }, "dependencies": { - "@headlessui/react": "^1.6.4", + "@headlessui/react": "^1.6.6", "@heroicons/react": "^1.0.6", "@radix-ui/react-context-menu": "^0.1.6", "clsx": "^1.1.1", diff --git a/packages/ui/src/Input.tsx b/packages/ui/src/Input.tsx index 5c32fc513..5120351fc 100644 --- a/packages/ui/src/Input.tsx +++ b/packages/ui/src/Input.tsx @@ -39,7 +39,7 @@ export const Input = React.forwardRef(({ ...props ref={ref} {...props} className={clsx( - `px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`, + `px-3 py-1 text-sm rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`, variants[props.variant || 'default'], props.className )} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14a3699c6b966807c5ac9c5ebb79fdfd1032433f..b4f6a891dba6a12003c55a2d11b8b936f9619afb 100644 GIT binary patch delta 1885 zcma)7Yiv_>6wiI%%U~^gF;)r%$K1oUyKUD8fw*V4whq>fZ8#aw$6edCU7v%t;uAjb zK^bsmXBdefDkL~T=lG)rAtu5HCJ!|+8WS-n8b?F|7$ro!v^+$k;mgfE_x#Q|zw`T_ z|G8t=7G8t0gHu#_tspqfyuqn=ngm|wbn1DhS+8>mPN&hOcNuxJ(J1id8jm=?${{{m zYY;Euit!10UV%8tJ|rIJ9+p0`;+eu+@z)Ywyr!B}7!#cCM0IpXh@}>(R_W9_y_z?R zwTi>ytGTmT|6oFh^*i0-4+12?p}0`(5nSP+LAM~~HmPgWym+9p$aA`kAZ8*)N(>(TG$%q}x#|+@S zfYo)iS!{{WfO&;aVBi~q?S{Bb&<{D>-ED~#y}m|oOVp!n zNm(7~bSUm=_bhMjY3a1+b^68zzbQ26>o5$3^aeOcoG8^udN zl_N>BxEEgylQ%FHRKqxfE@<#)^h_4zfw56s0~bf}+zb}I?ZG#b|KSA5xend%{j41I4Ijn$kCDsg=T1f^Kx00{Sg+)uTV(9UaDLBC{@aadmts5(&o=@oINC7>?D|0^d(p0PQ7L-3O|^`;(;L=j)Px*>&ujlsFu6bZH?xDIW^*-xpg3N;KXM`4zoar7DZ3~F_z!H_?(G!j|cVKVpk^Qr!IotA-h z7JG-$>gY{dd;58NZ->JdUFwes4e3-O-DMc?y0qqSH_ZHunFrHvvN`ZgA6EQe<@c1DZsy9cTVgb$xXr!3r0* zemS=xZ{c?C;w?Jgr%~&@TDQ;S)>yonZjaC6^_sOBtw-z8`HXsl(W7&_)oP#UCY4Jn z=4>hyD|xwiflLzh3XO=B$!w>tNM|Y1h_5R?6$h0nN$V6hcc(4YWacoGbg&eBDwz-k zlr6rS%7`5(ap}vacNp=SB4uf(aEug9S;esUHNisYBZ6L9DVnE5SFZbiX<|c*H?+iZ z21a(`%1^+*MCQTe4)QR}Op|g*e4adj*R-Trk2yd2Edpu1h!^|GGcqXA(8(a{Ckx{K z&wEHYmi*zY`=(s@dxFfuo3rGxE6|@s3-Cf8#p5#pYTXH2`$z#Ve?i?K@cJc6!NN>B zBjCby3d_WuTXd7Zsl`|wY;joZ)lJ=l);71Vwq7^jYN(I-o4gu7gp-(TxY0}{z}hMj;N$BQ zi;KUf-b;kQO-crf?=wl5-l8s&aB>_882wEBnh7gc$tQ3%hZgc+^&BPOy()B&z#D3G zN&&MWX{e+1h{Ye&p|`VOH^-;K{vcxDn*e$Pig%$j*bE>Vg0Ju#w7X~lj|7n~2X-Y> z0{n0iG5DENXuB&ue8(TC7CpEGGXF%0_^&%?n!=avp`v&?kWJGx-b>Q&kdXf@D`08= zrUE)6Qu$8~eTcwQPtv_6$fBt?N>Y9FAMX1zcDD;&1yRESin^|S{54q}&jQV`TUfpm@Y`CzzqGd1y z?gb_jcSh*)vv8t<7hp%p(D;|fnU#TfZd=Eht_;{$A_4W<9HZdk>h?Tynt+>!B=y>d z%&LIrKVyD;3o-;&hHb4Z(F3oJF$s8Tja@d!X`H;vR;I#t-K+rrCU6|iOy<5KutLUd z(Bj<}1-vJX%Ovo4I=7vEKVt!=R2+|2_i%GnaDGKX<8qX%dITniB({E=;L>s6W$w;2 zKAz3D<>T}!zLJ0ineg3l%X1A4xw AOaK4? From c0bc7745e3996f7d07f9f22ae2b3c5ea8672eaf2 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 11 Jul 2022 16:38:02 +0800 Subject: [PATCH 14/51] allow events to work from web --- apps/server/src/main.rs | 171 +++++++++++++----- apps/web/src/App.tsx | 30 ++- core/src/lib.rs | 8 +- .../interface/src/components/file/Sidebar.tsx | 17 +- .../src/screens/settings/LocationSettings.tsx | 6 +- 5 files changed, 164 insertions(+), 68 deletions(-) diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index 5d7c85331..45077b1d2 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -1,9 +1,15 @@ use sdcore::{ClientCommand, ClientQuery, CoreEvent, CoreResponse, Node, NodeController}; -use std::{env, path::Path}; +use std::{ + collections::HashSet, + env, + path::Path, + sync::{Arc, RwLock}, + time::{Duration, Instant}, +}; use actix::{ - Actor, AsyncContext, ContextFutureSpawner, Handler, Message, StreamHandler, - WrapFuture, + Actor, ActorContext, Addr, AsyncContext, Context, ContextFutureSpawner, Handler, + Message, StreamHandler, WrapFuture, }; use actix_web::{ get, http::StatusCode, web, App, Error, HttpRequest, HttpResponse, HttpServer, @@ -16,12 +22,84 @@ use tokio::sync::mpsc; const DATA_DIR_ENV_VAR: &'static str = "DATA_DIR"; -/// Define HTTP actor -struct Socket { - _event_receiver: web::Data>, - core: web::Data, +#[derive(Serialize)] +pub struct Event(CoreEvent); + +impl Message for Event { + type Result = (); } +struct EventServer { + clients: Arc>>>, +} + +impl Actor for EventServer { + type Context = Context; +} + +impl EventServer { + pub fn listen(mut event_receiver: mpsc::Receiver) -> Addr { + let server = Self { + clients: Arc::new(RwLock::new(HashSet::new())), + }; + let clients = server.clients.clone(); + tokio::spawn(async move { + let mut last = Instant::now(); + while let Some(event) = event_receiver.recv().await { + match event { + CoreEvent::InvalidateQueryDebounced(_) => { + let current = Instant::now(); + if current.duration_since(last) > Duration::from_millis(1000 / 60) + { + last = current; + for client in clients.read().unwrap().iter() { + client.do_send(Event(event.clone())); + } + } + }, + event => { + for client in clients.read().unwrap().iter() { + client.do_send(Event(event.clone())); + } + }, + } + } + }); + server.start() + } +} + +enum EventServerOperation { + Connect(Addr), + Disconnect(Addr), +} + +impl Message for EventServerOperation { + type Result = (); +} + +impl Handler for EventServer { + type Result = (); + + fn handle( + &mut self, + msg: EventServerOperation, + _: &mut Context, + ) -> Self::Result { + match msg { + EventServerOperation::Connect(addr) => { + self.clients.write().unwrap().insert(addr) + }, + EventServerOperation::Disconnect(addr) => { + self.clients.write().unwrap().remove(&addr) + }, + }; + } +} + +/// Define HTTP actor +struct Socket(web::Data, web::Data>); + impl Actor for Socket { type Context = ws::WebsocketContext; } @@ -62,18 +140,20 @@ impl StreamHandler> for Socket { }, }; - let core = self.core.clone(); + let core = self.0.clone(); + self.1.do_send(EventServerOperation::Connect(ctx.address())); let recipient = ctx.address().recipient(); - let fut = async move { match msg.payload { SocketMessagePayload::Query(query) => { match core.query(query).await { - Ok(response) => recipient.do_send(SocketResponse { - id: msg.id.clone(), - payload: SocketResponsePayload::Query(response), - }), + Ok(response) => { + recipient.do_send(SocketResponse::Response { + id: msg.id.clone(), + payload: response, + }) + }, Err(err) => { println!("query error: {:?}", err); // Err(err.to_string()) @@ -82,10 +162,12 @@ impl StreamHandler> for Socket { }, SocketMessagePayload::Command(command) => { match core.command(command).await { - Ok(response) => recipient.do_send(SocketResponse { - id: msg.id.clone(), - payload: SocketResponsePayload::Query(response), - }), + Ok(response) => { + recipient.do_send(SocketResponse::Response { + id: msg.id.clone(), + payload: response, + }) + }, Err(err) => { println!("command error: {:?}", err); // Err(err.to_string()) @@ -102,27 +184,35 @@ impl StreamHandler> for Socket { _ => (), } } + + fn finished(&mut self, ctx: &mut Self::Context) { + self.1 + .do_send(EventServerOperation::Disconnect(ctx.address())); + ctx.stop(); + } } -#[derive(Serialize)] -#[serde(rename_all = "camelCase", tag = "type", content = "data")] -pub enum SocketResponsePayload { - Query(CoreResponse), +impl Handler for Socket { + type Result = (); + + fn handle(&mut self, msg: Event, ctx: &mut Self::Context) { + ctx.text(serde_json::to_string(&SocketResponse::Event(msg.0)).unwrap()); + } } #[derive(Message, Serialize)] +#[serde(rename_all = "camelCase", tag = "type", content = "data")] #[rtype(result = "()")] -struct SocketResponse { - id: String, - payload: SocketResponsePayload, +enum SocketResponse { + Response { id: String, payload: CoreResponse }, + Event(CoreEvent), } impl Handler for Socket { type Result = (); fn handle(&mut self, msg: SocketResponse, ctx: &mut Self::Context) { - let string = serde_json::to_string(&msg).unwrap(); - ctx.text(string); + ctx.text(serde_json::to_string(&msg).unwrap()); } } @@ -140,26 +230,13 @@ async fn healthcheck() -> impl Responder { async fn ws_handler( req: HttpRequest, stream: web::Payload, - event_receiver: web::Data>, controller: web::Data, + server: web::Data>, ) -> Result { - let resp = ws::start( - Socket { - _event_receiver: event_receiver, - core: controller, - }, - &req, - stream, - ); + let resp = ws::start(Socket(controller, server), &req, stream); resp } -#[get("/file/{file:.*}")] -async fn file() -> impl Responder { - // TODO - format!("OK") -} - async fn not_found() -> impl Responder { HttpResponse::build(StatusCode::OK).body("We're past the event horizon...") } @@ -168,15 +245,16 @@ async fn not_found() -> impl Responder { async fn main() -> std::io::Result<()> { let (event_receiver, controller) = setup().await; + let server = web::Data::new(EventServer::listen(event_receiver)); + println!("Listening http://localhost:8080"); HttpServer::new(move || { App::new() - .app_data(event_receiver.clone()) .app_data(controller.clone()) + .app_data(server.clone()) .service(index) .service(healthcheck) .service(ws_handler) - .service(file) .default_service(web::route().to(not_found)) }) .bind(("0.0.0.0", 8080))? @@ -184,10 +262,7 @@ async fn main() -> std::io::Result<()> { .await } -async fn setup() -> ( - web::Data>, - web::Data, -) { +async fn setup() -> (mpsc::Receiver, web::Data) { let data_dir_path = match env::var(DATA_DIR_ENV_VAR) { Ok(path) => Path::new(&path).to_path_buf(), Err(_e) => { @@ -207,5 +282,5 @@ async fn setup() -> ( let (controller, event_receiver, node) = Node::new(data_dir_path).await; tokio::spawn(node.start()); - (web::Data::new(event_receiver), web::Data::new(controller)) + (event_receiver, web::Data::new(controller)) } diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index f53220197..7ae21f2f4 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -34,7 +34,6 @@ class Transport extends BaseTransport { .then(() => { this.websocket = ws; this.attachEventListeners(); - console.log('Reconnected!'); }) .catch((err) => this.reconnect(timeoutIndex++)); }, timeout); @@ -44,33 +43,34 @@ class Transport extends BaseTransport { this.websocket.addEventListener('message', (event) => { if (!event.data) return; - const { id, payload } = JSON.parse(event.data); + const { type: msg_type, data: msg_data } = JSON.parse(event.data); - const { type, data } = payload; - if (type === 'event') { - this.emit('core_event', data); - } else if (type === 'query' || type === 'command') { + if (msg_type === 'response') { + const id = msg_data.id; if (this.requestMap.has(id)) { - this.requestMap.get(id)?.(data); + this.requestMap.get(id)?.({ data: msg_data.payload.data }); this.requestMap.delete(id); } + } else if (msg_type === 'event') { + this.emit('core_event', msg_data); + } else { + console.error(`Received response message of type ${msg_type} which is not valid!`); } }); this.websocket.addEventListener('close', () => { - console.log('GONE'); this.reconnect(); }); } async query(query: ClientQuery) { - if (websocket.readyState == 0) { + if (this.websocket.readyState == 0) { let resolve: () => void; const promise = new Promise((res) => { resolve = () => res(undefined); }); // @ts-ignore - websocket.addEventListener('open', resolve); + this.websocket.addEventListener('open', resolve); await promise; } @@ -89,6 +89,16 @@ class Transport extends BaseTransport { return await promise; } async command(command: ClientCommand) { + if (this.websocket.readyState == 0) { + let resolve: () => void; + const promise = new Promise((res) => { + resolve = () => res(undefined); + }); + // @ts-ignore + this.websocket.addEventListener('open', resolve); + await promise; + } + const id = randomId(); let resolve: (data: any) => void; diff --git a/core/src/lib.rs b/core/src/lib.rs index 21153848b..dc16a1ce1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -365,7 +365,7 @@ pub enum LibraryCommand { } /// is a query destined for the core -#[derive(Serialize, Deserialize, Debug, TS)] +#[derive(Serialize, Deserialize, Debug, Clone, TS)] #[serde(tag = "key", content = "params")] #[ts(export)] pub enum ClientQuery { @@ -381,7 +381,7 @@ pub enum ClientQuery { } /// is a query destined for a specific library which is loaded into the core. -#[derive(Serialize, Deserialize, Debug, TS)] +#[derive(Serialize, Deserialize, Debug, Clone, TS)] #[serde(tag = "key", content = "params")] #[ts(export)] pub enum LibraryQuery { @@ -400,7 +400,7 @@ pub enum LibraryQuery { } // represents an event this library can emit -#[derive(Serialize, Deserialize, Debug, TS)] +#[derive(Serialize, Deserialize, Debug, Clone, TS)] #[serde(tag = "key", content = "data")] #[ts(export)] pub enum CoreEvent { @@ -455,7 +455,7 @@ pub enum CoreError { LibraryError(#[from] library::LibraryError), } -#[derive(Serialize, Deserialize, Debug, TS)] +#[derive(Serialize, Deserialize, Debug, Clone, TS)] #[ts(export)] pub enum CoreResource { Client, diff --git a/packages/interface/src/components/file/Sidebar.tsx b/packages/interface/src/components/file/Sidebar.tsx index d8d42a232..d203333b1 100644 --- a/packages/interface/src/components/file/Sidebar.tsx +++ b/packages/interface/src/components/file/Sidebar.tsx @@ -165,8 +165,21 @@ export const Sidebar: React.FC = (props) => { icon: CogIcon, onPress: () => navigate('library-settings/general') }, - { name: 'Add Library', icon: PlusIcon }, - { name: 'Lock', icon: LockClosedIcon } + { + name: 'Add Library', + icon: PlusIcon, + onPress: () => { + alert('todo'); + // TODO: Show Dialog defined in `LibrariesSettings.tsx` + } + }, + { + name: 'Lock', + icon: LockClosedIcon, + onPress: () => { + alert('todo'); + } + } // { name: 'Hide', icon: EyeOffIcon } ] ]} diff --git a/packages/interface/src/screens/settings/LocationSettings.tsx b/packages/interface/src/screens/settings/LocationSettings.tsx index 84f005a1e..1277af7a0 100644 --- a/packages/interface/src/screens/settings/LocationSettings.tsx +++ b/packages/interface/src/screens/settings/LocationSettings.tsx @@ -1,4 +1,4 @@ -import { useBridgeQuery } from '@sd/client'; +import { useLibraryQuery } from '@sd/client'; import React from 'react'; import LocationListItem from '../../components/location/LocationListItem'; @@ -13,9 +13,7 @@ import { SettingsHeader } from '../../components/settings/SettingsHeader'; // ]; export default function LocationSettings() { - const { data: locations } = useBridgeQuery('SysGetLocations'); - - console.log({ locations }); + const { data: locations } = useLibraryQuery('SysGetLocations'); return ( From cc2cbdb070581548d129e9b91039188c7ea11ab0 Mon Sep 17 00:00:00 2001 From: Alex <28069568+misxki@users.noreply.github.com> Date: Tue, 12 Jul 2022 12:33:00 +0100 Subject: [PATCH 15/51] SearchBar Logo fix (#310) * SearchBar Component * showing cmd+k for macOS&web Co-authored-by: Ahriox <28069568+Ahriox@users.noreply.github.com> --- .../src/components/layout/TopBar.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/interface/src/components/layout/TopBar.tsx b/packages/interface/src/components/layout/TopBar.tsx index 828666a01..151a880c3 100644 --- a/packages/interface/src/components/layout/TopBar.tsx +++ b/packages/interface/src/components/layout/TopBar.tsx @@ -13,8 +13,9 @@ import { Tag, TerminalWindow } from 'phosphor-react'; -import React, { DetailedHTMLProps, HTMLAttributes } from 'react'; +import React, { DetailedHTMLProps, HTMLAttributes, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; +import { AppPropsContext } from '../../AppPropsContext'; import { Shortcut } from '../primitive/Shortcut'; import { DefaultProps } from '../primitive/types'; @@ -28,6 +29,7 @@ export interface TopBarButtonProps left?: boolean; right?: boolean; } +interface SearchBarProps extends DefaultProps {} const TopBarButton: React.FC = ({ icon: Icon, ...props }) => { return ( @@ -49,6 +51,23 @@ const TopBarButton: React.FC = ({ icon: Icon, ...props }) => ); }; +const SearchBar: React.FC = (props) => { //TODO: maybe pass the appProps, so we can have the context in the TopBar if needed again + const appProps = useContext(AppPropsContext); + + return ( +
+ +
+ + {/* */} +
+
+ ); +} + export const TopBar: React.FC = (props) => { const { locationId } = useExplorerStore(); const { mutate: generateThumbsForLocation } = useLibraryCommand('GenerateThumbsForLocation', { @@ -88,16 +107,7 @@ export const TopBar: React.FC = (props) => {
-
- -
- - {/* */} -
-
+
From 59dbbde33cc3a6a5560df5a7283ee746ed8375c2 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Tue, 12 Jul 2022 19:33:04 +0800 Subject: [PATCH 16/51] make the `Socket` have named fields for clarity --- apps/server/src/main.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index 45077b1d2..7f74760fb 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -98,7 +98,10 @@ impl Handler for EventServer { } /// Define HTTP actor -struct Socket(web::Data, web::Data>); +struct Socket { + node_controller: web::Data, + event_server: web::Data>, +} impl Actor for Socket { type Context = ws::WebsocketContext; @@ -140,8 +143,9 @@ impl StreamHandler> for Socket { }, }; - let core = self.0.clone(); - self.1.do_send(EventServerOperation::Connect(ctx.address())); + let core = self.node_controller.clone(); + self.event_server + .do_send(EventServerOperation::Connect(ctx.address())); let recipient = ctx.address().recipient(); let fut = async move { @@ -186,7 +190,7 @@ impl StreamHandler> for Socket { } fn finished(&mut self, ctx: &mut Self::Context) { - self.1 + self.event_server .do_send(EventServerOperation::Disconnect(ctx.address())); ctx.stop(); } @@ -233,7 +237,14 @@ async fn ws_handler( controller: web::Data, server: web::Data>, ) -> Result { - let resp = ws::start(Socket(controller, server), &req, stream); + let resp = ws::start( + Socket { + node_controller: controller, + event_server: server, + }, + &req, + stream, + ); resp } From 75f9e34a6c69bc52a9d27590bb2dac8449f56871 Mon Sep 17 00:00:00 2001 From: Jamie Pine <32987599+jamiepine@users.noreply.github.com> Date: Thu, 14 Jul 2022 13:50:48 -0700 Subject: [PATCH 17/51] Explorer Grid View (#334) added grid view, as well as: - moved location context to client lib - merged library settings with main settings - added some missing settings - removed demo locations due to FileItem props syntax change, they are currently being replaced anyway by Oscar in another PR - added functioning favorite button to the inspector, that works now --- .vscode/settings.json | 1 + core/bindings/LibraryCommand.ts | 2 +- core/src/file/mod.rs | 27 +++ core/src/lib.rs | 12 +- core/src/library/library_manager.rs | 4 +- packages/client/src/bridge.ts | 8 +- .../client/src/context/AppPropsContext.tsx | 1 + .../client/src/context/LocationContext.ts | 9 + packages/client/src/context/index.ts | 1 + .../client/src/stores/useExplorerStore.ts | 6 + packages/interface/package.json | 2 + packages/interface/src/App.tsx | 32 ++- packages/interface/src/AppLayout.tsx | 2 +- packages/interface/src/AppRouter.tsx | 22 +- .../src/components/device/Device.tsx | 7 +- .../src/components/file/FileItem.tsx | 210 +++++++----------- .../src/components/file/FileList.tsx | 156 ++++++++----- .../src/components/file/FileThumb.tsx | 13 +- .../src/components/file/Inspector.tsx | 167 +++++++------- .../interface/src/components/file/Sidebar.tsx | 12 +- .../src/components/layout/TopBar.tsx | 65 +++--- .../components/location/LocationListItem.tsx | 3 +- .../settings/SettingsScreenContainer.tsx | 2 +- packages/interface/src/screens/Content.tsx | 18 +- packages/interface/src/screens/Explorer.tsx | 2 +- packages/interface/src/screens/Overview.tsx | 37 +-- .../settings/CurrentLibrarySettings.tsx | 42 ---- .../src/screens/settings/LocationSettings.tsx | 30 --- .../src/screens/settings/Settings.tsx | 113 ++++++---- .../settings/client/ExtensionsSettings.tsx | 83 +++++++ .../settings/client/KeybindSettings.tsx | 21 ++ .../BackupsSettings.tsx} | 2 +- .../library/LibraryGeneralSettings.tsx | 30 ++- .../settings/library/NodesSettings.tsx | 15 ++ .../settings/node/LibrariesSettings.tsx | 2 + packages/interface/src/style.scss | 2 +- pnpm-lock.yaml | Bin 625653 -> 630480 bytes 37 files changed, 655 insertions(+), 506 deletions(-) create mode 100644 packages/client/src/context/LocationContext.ts delete mode 100644 packages/interface/src/screens/settings/CurrentLibrarySettings.tsx delete mode 100644 packages/interface/src/screens/settings/LocationSettings.tsx create mode 100644 packages/interface/src/screens/settings/client/ExtensionsSettings.tsx create mode 100644 packages/interface/src/screens/settings/client/KeybindSettings.tsx rename packages/interface/src/screens/settings/{node/NodesSettings.tsx => library/BackupsSettings.tsx} (75%) create mode 100644 packages/interface/src/screens/settings/library/NodesSettings.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index cedc4f182..a21f04485 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "ipfs", "Keepsafe", "nodestate", + "overscan", "pathctx", "prismjs", "proptype", diff --git a/core/bindings/LibraryCommand.ts b/core/bindings/LibraryCommand.ts index 713fc8989..d99033a9f 100644 --- a/core/bindings/LibraryCommand.ts +++ b/core/bindings/LibraryCommand.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type LibraryCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { name: string, color: string, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocRescan", params: { id: number, } } | { key: "SysVolumeUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } }; \ No newline at end of file +export type LibraryCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileSetFavorite", params: { id: number, favorite: boolean, } } | { key: "FileDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { name: string, color: string, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocFullRescan", params: { id: number, } } | { key: "LocQuickRescan", params: { id: number, } } | { key: "SysVolumeUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } }; \ No newline at end of file diff --git a/core/src/file/mod.rs b/core/src/file/mod.rs index c8955e7f1..780633b93 100644 --- a/core/src/file/mod.rs +++ b/core/src/file/mod.rs @@ -168,3 +168,30 @@ pub async fn set_note( Ok(CoreResponse::Success(())) } + +pub async fn favorite( + ctx: LibraryContext, + id: i32, + favorite: bool, +) -> Result { + let _response = ctx + .db + .file() + .find_unique(file::id::equals(id)) + .update(vec![file::favorite::set(favorite)]) + .exec() + .await + .unwrap(); + + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::LibGetExplorerDir { + limit: 0, + path: "".to_string(), + location_id: 0, + }, + })) + .await; + + Ok(CoreResponse::Success(())) +} diff --git a/core/src/lib.rs b/core/src/lib.rs index dc16a1ce1..f865b766b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -175,7 +175,7 @@ impl Node { description, } => { self.library_manager - .edit_library(id, name, description) + .edit(id, name, description) .await .unwrap(); CoreResponse::Success(()) @@ -210,15 +210,19 @@ impl Node { sys::delete_location(&ctx, id).await?; CoreResponse::Success(()) } - LibraryCommand::LocRescan { id } => { + LibraryCommand::LocFullRescan { id } => { sys::scan_location(&ctx, id, String::new()).await; CoreResponse::Success(()) } + LibraryCommand::LocQuickRescan { id: _ } => todo!(), // CRUD for files LibraryCommand::FileReadMetaData { id: _ } => todo!(), LibraryCommand::FileSetNote { id, note } => { file::set_note(ctx, id, note).await? } + LibraryCommand::FileSetFavorite { id, favorite } => { + file::favorite(ctx, id, favorite).await? + } // ClientCommand::FileEncrypt { id: _, algorithm: _ } => todo!(), LibraryCommand::FileDelete { id } => { ctx.db @@ -345,6 +349,7 @@ pub enum LibraryCommand { // Files FileReadMetaData { id: i32 }, FileSetNote { id: i32, note: Option }, + FileSetFavorite { id: i32, favorite: bool }, // FileEncrypt { id: i32, algorithm: EncryptionAlgorithm }, FileDelete { id: i32 }, // Tags @@ -356,7 +361,8 @@ pub enum LibraryCommand { LocCreate { path: String }, LocUpdate { id: i32, name: Option }, LocDelete { id: i32 }, - LocRescan { id: i32 }, + LocFullRescan { id: i32 }, + LocQuickRescan { id: i32 }, // System SysVolumeUnmount { id: i32 }, GenerateThumbsForLocation { id: i32, path: String }, diff --git a/core/src/library/library_manager.rs b/core/src/library/library_manager.rs index a4da40acd..5c1aca635 100644 --- a/core/src/library/library_manager.rs +++ b/core/src/library/library_manager.rs @@ -156,7 +156,7 @@ impl LibraryManager { .collect() } - pub(crate) async fn edit_library( + pub(crate) async fn edit( &self, id: String, name: Option, @@ -223,7 +223,7 @@ impl LibraryManager { .find(|lib| lib.id.to_string() == library_id) .map(|v| v.clone()) } - + /// load the library from a given path pub(crate) async fn load( id: Uuid, diff --git a/packages/client/src/bridge.ts b/packages/client/src/bridge.ts index 41e430c66..1c81f3519 100644 --- a/packages/client/src/bridge.ts +++ b/packages/client/src/bridge.ts @@ -18,10 +18,10 @@ export function setTransport(_transport: BaseTransport) { } // extract keys from generated Rust query/command types -type QueryKeyType = ClientQuery['key']; -type LibraryQueryKeyType = LibraryQuery['key']; -type CommandKeyType = ClientCommand['key']; -type LibraryCommandKeyType = LibraryCommand['key']; +export type QueryKeyType = ClientQuery['key']; +export type LibraryQueryKeyType = LibraryQuery['key']; +export type CommandKeyType = ClientCommand['key']; +export type LibraryCommandKeyType = LibraryCommand['key']; // extract the type from the union type CQType = Extract; diff --git a/packages/client/src/context/AppPropsContext.tsx b/packages/client/src/context/AppPropsContext.tsx index 86876b309..d0f74afe9 100644 --- a/packages/client/src/context/AppPropsContext.tsx +++ b/packages/client/src/context/AppPropsContext.tsx @@ -10,6 +10,7 @@ export interface AppProps { transport: BaseTransport; platform: Platform; cdn_url?: CdnUrl; + data_path?: string; convertFileSrc: (url: string) => string; openDialog: (options: { directory?: boolean }) => Promise; onClose?: () => void; diff --git a/packages/client/src/context/LocationContext.ts b/packages/client/src/context/LocationContext.ts new file mode 100644 index 000000000..379ffa334 --- /dev/null +++ b/packages/client/src/context/LocationContext.ts @@ -0,0 +1,9 @@ +import { createContext } from 'react'; + +export const LocationContext = createContext<{ + location_id: number; + data_path: string; +}>({ + location_id: 1, + data_path: '' +}); diff --git a/packages/client/src/context/index.ts b/packages/client/src/context/index.ts index 70d70d64e..7e250580f 100644 --- a/packages/client/src/context/index.ts +++ b/packages/client/src/context/index.ts @@ -1 +1,2 @@ export * from './AppPropsContext'; +export * from './LocationContext'; diff --git a/packages/client/src/stores/useExplorerStore.ts b/packages/client/src/stores/useExplorerStore.ts index 185d88dd1..c16399e34 100644 --- a/packages/client/src/stores/useExplorerStore.ts +++ b/packages/client/src/stores/useExplorerStore.ts @@ -1,16 +1,21 @@ import create from 'zustand'; +type LayoutMode = 'list' | 'grid'; + type ExplorerStore = { selectedRowIndex: number; + layoutMode: LayoutMode; setSelectedRowIndex: (index: number) => void; locationId: number; setLocationId: (index: number) => void; newThumbnails: Record; addNewThumbnail: (cas_id: string) => void; + setLayoutMode: (mode: LayoutMode) => void; reset: () => void; }; export const useExplorerStore = create((set) => ({ + layoutMode: 'grid', selectedRowIndex: 1, setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })), locationId: -1, @@ -21,5 +26,6 @@ export const useExplorerStore = create((set) => ({ ...state, newThumbnails: { ...state.newThumbnails, [cas_id]: true } })), + setLayoutMode: (mode: LayoutMode) => set((state) => ({ ...state, layoutMode: mode })), reset: () => set(() => ({})) })); diff --git a/packages/interface/package.json b/packages/interface/package.json index 8577584cd..02e05a74d 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -26,6 +26,7 @@ "@sd/client": "workspace:*", "@sd/core": "workspace:*", "@sd/ui": "workspace:*", + "@types/styled-components": "^5.1.25", "@vitejs/plugin-react": "^1.3.2", "autoprefixer": "^10.4.7", "byte-size": "^8.1.0", @@ -54,6 +55,7 @@ "react-transition-group": "^4.4.2", "react-virtuoso": "^2.12.1", "rooks": "^5.11.2", + "styled-components": "^5.3.5", "tailwindcss": "^3.0.24", "use-debounce": "^8.0.1", "zustand": "4.0.0-rc.1" diff --git a/packages/interface/src/App.tsx b/packages/interface/src/App.tsx index 514d60a36..a04b4382f 100644 --- a/packages/interface/src/App.tsx +++ b/packages/interface/src/App.tsx @@ -1,8 +1,8 @@ import '@fontsource/inter/variable.css'; -import { BaseTransport, ClientProvider, setTransport } from '@sd/client'; +import { BaseTransport, ClientProvider, setTransport, useBridgeQuery } from '@sd/client'; import { useCoreEvents } from '@sd/client'; import { AppProps, AppPropsContext } from '@sd/client'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { QueryClient, QueryClientProvider } from 'react-query'; import { MemoryRouter } from 'react-router-dom'; @@ -13,12 +13,24 @@ import './style.scss'; const queryClient = new QueryClient(); -function RouterContainer() { +function RouterContainer(props: { props: AppProps }) { useCoreEvents(); + const [appProps, setAppProps] = useState(props.props); + const { data: client } = useBridgeQuery('NodeGetState'); + + useEffect(() => { + setAppProps({ + ...appProps, + data_path: client?.data_path + }); + }, [client?.data_path]); + return ( - - - + + + + + ); } @@ -34,11 +46,9 @@ export default function App(props: AppProps) { <> {}}> - - - - - + + + diff --git a/packages/interface/src/AppLayout.tsx b/packages/interface/src/AppLayout.tsx index 0c3ecbb11..ed5715a23 100644 --- a/packages/interface/src/AppLayout.tsx +++ b/packages/interface/src/AppLayout.tsx @@ -1,4 +1,4 @@ -import { AppPropsContext } from '@sd/client'; +import { AppPropsContext, useBridgeQuery } from '@sd/client'; import clsx from 'clsx'; import React, { useContext } from 'react'; import { Outlet } from 'react-router-dom'; diff --git a/packages/interface/src/AppRouter.tsx b/packages/interface/src/AppRouter.tsx index db0ffdacd..640d3f1eb 100644 --- a/packages/interface/src/AppRouter.tsx +++ b/packages/interface/src/AppRouter.tsx @@ -12,21 +12,22 @@ import { OverviewScreen } from './screens/Overview'; import { PhotosScreen } from './screens/Photos'; import { RedirectPage } from './screens/Redirect'; import { TagScreen } from './screens/Tag'; -import { CurrentLibrarySettings } from './screens/settings/CurrentLibrarySettings'; import { SettingsScreen } from './screens/settings/Settings'; import AppearanceSettings from './screens/settings/client/AppearanceSettings'; +import ExtensionSettings from './screens/settings/client/ExtensionsSettings'; import GeneralSettings from './screens/settings/client/GeneralSettings'; +import KeybindSettings from './screens/settings/client/KeybindSettings'; import ContactsSettings from './screens/settings/library/ContactsSettings'; import KeysSettings from './screens/settings/library/KeysSetting'; import LibraryGeneralSettings from './screens/settings/library/LibraryGeneralSettings'; import LocationSettings from './screens/settings/library/LocationSettings'; +import NodesSettings from './screens/settings/library/NodesSettings'; import SecuritySettings from './screens/settings/library/SecuritySettings'; import SharingSettings from './screens/settings/library/SharingSettings'; import SyncSettings from './screens/settings/library/SyncSettings'; import TagsSettings from './screens/settings/library/TagsSettings'; import ExperimentalSettings from './screens/settings/node/ExperimentalSettings'; import LibrarySettings from './screens/settings/node/LibrariesSettings'; -import NodesSettings from './screens/settings/node/NodesSettings'; import P2PSettings from './screens/settings/node/P2PSettings'; export function AppRouter() { @@ -57,28 +58,27 @@ export function AppRouter() { } /> } /> } /> - }> - } /> - } /> - } /> - } /> - } /> - }> } /> } /> } /> - } /> + } /> + } /> } /> } /> } /> } /> - } /> + } /> } /> } /> } /> } /> } /> + } /> + } /> + } /> + } /> + } /> } /> } /> diff --git a/packages/interface/src/components/device/Device.tsx b/packages/interface/src/components/device/Device.tsx index 6f91b2e26..2cfd835b1 100644 --- a/packages/interface/src/components/device/Device.tsx +++ b/packages/interface/src/components/device/Device.tsx @@ -72,12 +72,11 @@ export function Device(props: DeviceProps) { key={key} selected={selectedFile === location.name} onClick={() => handleSelect(location.name)} - fileName={location.name} - folder={location.folder} - format={location.format} - iconName={location.icon} /> ))} + {props.locations.length === 0 && ( +
No locations
+ )}
); diff --git a/packages/interface/src/components/file/FileItem.tsx b/packages/interface/src/components/file/FileItem.tsx index 509cec97c..b9598bf16 100644 --- a/packages/interface/src/components/file/FileItem.tsx +++ b/packages/interface/src/components/file/FileItem.tsx @@ -1,151 +1,109 @@ +import { LocationContext, useExplorerStore } from '@sd/client'; +import { File, FilePath } from '@sd/core'; import clsx from 'clsx'; import { FilePlus, FileText, Plus, Share, Trash } from 'phosphor-react'; -import React, { MouseEventHandler } from 'react'; +import React, { MouseEventHandler, useContext } from 'react'; import icons from '../../assets/icons'; import { ReactComponent as Folder } from '../../assets/svg/folder.svg'; import { WithContextMenu } from '../layout/MenuOverlay'; import { DefaultProps } from '../primitive/types'; +import FileThumb from './FileThumb'; -interface Props extends DefaultProps { - fileName: string; - iconName?: string; - format?: string; - folder?: boolean; +interface Props extends React.HTMLAttributes { + file?: FilePath | null; selected?: boolean; - onClick?: MouseEventHandler; } export default function FileItem(props: Props) { - // const Shadow = () => { - // return ( - //
- // ); - // }; + const location = useContext(LocationContext); return ( - +
-
-
- {props.folder ? ( -
-
- -
+ )} + > + {props.file?.is_dir ? ( +
+
+
- ) : ( -
- - - - - - -
- {props.iconName && icons[props.iconName as keyof typeof icons] ? ( - (() => { - const Icon = icons[props.iconName as keyof typeof icons]; - return ( - - ); - })() - ) : ( - <> - )} - - {props.format} - -
-
- )} -
-
- + ) : props.file?.file?.has_thumbnail ? ( +
+
+ +
+
+ ) : ( +
- {props.fileName} - -
+ + + + + + +
+ {props.file?.extension && icons[props.file.extension as keyof typeof icons] ? ( + (() => { + const Icon = icons[props.file.extension as keyof typeof icons]; + return ( + + ); + })() + ) : ( + <> + )} + + {props.file?.extension} + +
+
+ )}
- +
+ + {props.file?.name} + +
+
); } diff --git a/packages/interface/src/components/file/FileList.tsx b/packages/interface/src/components/file/FileList.tsx index 3409bdf2f..e2a792a60 100644 --- a/packages/interface/src/components/file/FileList.tsx +++ b/packages/interface/src/components/file/FileList.tsx @@ -1,14 +1,16 @@ import { DotsVerticalIcon } from '@heroicons/react/solid'; -import { useBridgeQuery, useLibraryQuery } from '@sd/client'; +import { LocationContext, useBridgeQuery, useLibraryQuery } from '@sd/client'; import { useExplorerStore } from '@sd/client'; import { AppPropsContext } from '@sd/client'; import { FilePath } from '@sd/core'; import clsx from 'clsx'; import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'; +import { Virtuoso, VirtuosoGrid, VirtuosoHandle } from 'react-virtuoso'; import { useKey, useWindowSize } from 'rooks'; +import styled from 'styled-components'; +import FileItem from './FileItem'; import FileThumb from './FileThumb'; interface IColumn { @@ -32,13 +34,18 @@ const columns = ensureIsColumns([ type ColumnKey = typeof columns[number]['key']; -const LocationContext = React.createContext<{ - location_id: number; - data_path: string; -}>({ - location_id: 1, - data_path: '' -}); +// these styled components are out of place, but are here to follow the virtuoso docs. could probably be translated to tailwind somehow, since the `components` prop only accepts a styled div, not a react component. +const GridContainer = styled.div` + display: flex; + margin-top: 60px; + margin-left: 10px; + width: 100%; + flex-wrap: wrap; +`; +const GridItemContainer = styled.div` + display: flex; + flex-wrap: wrap; +`; export const FileList: React.FC<{ location_id: number; path: string; limit: number }> = (props) => { const size = useWindowSize(); @@ -51,7 +58,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb const path = props.path; - const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerStore(); + const { selectedRowIndex, setSelectedRowIndex, setLocationId, layoutMode } = useExplorerStore(); const [goingUp, setGoingUp] = useState(false); const { data: currentDir } = useLibraryQuery('LibGetExplorerDir', { @@ -64,7 +71,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb if (selectedRowIndex === 0 && goingUp) { VList.current?.scrollTo({ top: 0, behavior: 'smooth' }); } - if (selectedRowIndex != -1) { + if (selectedRowIndex != -1 && typeof VList.current?.scrollIntoView === 'function') { VList.current?.scrollIntoView({ index: goingUp ? selectedRowIndex - 1 : selectedRowIndex }); @@ -88,12 +95,12 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb setSelectedRowIndex(selectedRowIndex + 1); }); - const Row = (index: number) => { - const row = currentDir?.contents?.[index]; - - if (!row) return null; - - return ; + const createRenderItem = (RenderItem: React.FC) => { + return (index: number) => { + const row = currentDir?.contents?.[index]; + if (!row) return null; + return ; + }; }; const Header = () => ( @@ -116,46 +123,90 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
); - return useMemo( - () => ( -
+ - + {layoutMode === 'grid' && ( + + )} + {layoutMode === 'list' && (
}} + itemContent={createRenderItem(RenderRow)} + components={{ + Header, + Footer: () =>
+ }} increaseViewportBy={{ top: 400, bottom: 200 }} className="outline-none explorer-scroll" /> - -
- ), - [props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current] + )} + +
); }; -const RenderRow: React.FC<{ - row: FilePath; - rowIndex: number; +interface RenderItemProps { + item: FilePath; + index: number; dirId: number; -}> = ({ row, rowIndex, dirId }) => { +} + +const RenderGridItem: React.FC = ({ item, index, dirId }) => { + // return
; const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore(); - const isActive = selectedRowIndex === rowIndex; + const isActive = selectedRowIndex === index; let [_, setSearchParams] = useSearchParams(); function selectFileHandler() { - if (selectedRowIndex == rowIndex) setSelectedRowIndex(-1); - else setSelectedRowIndex(rowIndex); + if (selectedRowIndex == index) setSelectedRowIndex(-1); + else setSelectedRowIndex(index); + } + + return ( + { + if (item.is_dir) { + setSearchParams({ path: item.materialized_path }); + } + }} + file={item} + selected={isActive} + onClick={selectFileHandler} + /> + ); +}; + +const RenderRow: React.FC = ({ item, index, dirId }) => { + const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore(); + const isActive = selectedRowIndex === index; + + let [_, setSearchParams] = useSearchParams(); + + function selectFileHandler() { + if (selectedRowIndex == index) setSelectedRowIndex(-1); + else setSelectedRowIndex(index); } return useMemo( @@ -163,14 +214,14 @@ const RenderRow: React.FC<{
{ - if (row.is_dir) { - setSearchParams({ path: row.materialized_path }); + if (item.is_dir) { + setSearchParams({ path: item.materialized_path }); } }} className={clsx( 'table-body-row mr-2 flex flex-row rounded-lg border-2', isActive ? 'border-primary-500' : 'border-transparent', - rowIndex % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]' + index % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]' )} > {columns.map((col) => ( @@ -179,12 +230,12 @@ const RenderRow: React.FC<{ className="flex items-center px-4 py-2 pr-2 table-body-cell" style={{ width: col.width }} > - +
))}
), - [row.id, isActive] + [item.id, isActive] ); }; @@ -193,29 +244,22 @@ const RenderCell: React.FC<{ dirId?: number; file?: FilePath; }> = ({ colKey, file, dirId }) => { + const location = useContext(LocationContext); + if (!file || !colKey || !dirId) return <>; + const row = file; if (!row) return <>; - const appProps = useContext(AppPropsContext); const value = row[colKey]; if (!value) return <>; - const location = useContext(LocationContext); - const { newThumbnails } = useExplorerStore(); - - const hasNewThumbnail = !!newThumbnails[row?.file?.cas_id ?? '']; - switch (colKey) { case 'name': return (
- +
{/* {colKey == 'name' && (() => { diff --git a/packages/interface/src/components/file/FileThumb.tsx b/packages/interface/src/components/file/FileThumb.tsx index 7ae390f8c..62164a88d 100644 --- a/packages/interface/src/components/file/FileThumb.tsx +++ b/packages/interface/src/components/file/FileThumb.tsx @@ -1,4 +1,4 @@ -import { useBridgeQuery } from '@sd/client'; +import { useBridgeQuery, useExplorerStore } from '@sd/client'; import { AppPropsContext } from '@sd/client'; import { FilePath } from '@sd/core'; import clsx from 'clsx'; @@ -10,22 +10,23 @@ import { Folder } from '../icons/Folder'; export default function FileThumb(props: { file: FilePath; locationId: number; - hasThumbnailOverride: boolean; className?: string; }) { const appProps = useContext(AppPropsContext); - const { data: client } = useBridgeQuery('NodeGetState'); + const { newThumbnails } = useExplorerStore(); + + const hasNewThumbnail = !!newThumbnails[props?.file?.file?.cas_id ?? '']; if (props.file.is_dir) { return ; } - if (client?.data_path && (props.file.file?.has_thumbnail || props.hasThumbnailOverride)) { + if (appProps?.data_path && (props.file.file?.has_thumbnail || hasNewThumbnail)) { return ( ); diff --git a/packages/interface/src/components/file/Inspector.tsx b/packages/interface/src/components/file/Inspector.tsx index 5f10760ba..42b2d127a 100644 --- a/packages/interface/src/components/file/Inspector.tsx +++ b/packages/interface/src/components/file/Inspector.tsx @@ -1,11 +1,11 @@ import { Transition } from '@headlessui/react'; import { ShareIcon } from '@heroicons/react/solid'; -import { useInspectorStore } from '@sd/client'; +import { useInspectorStore, useLibraryCommand } from '@sd/client'; import { FilePath, LocationResource } from '@sd/core'; import { Button, TextArea } from '@sd/ui'; import moment from 'moment'; import { Heart, Link } from 'phosphor-react'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { default as types } from '../../constants/file-types.json'; import FileThumb from './FileThumb'; @@ -44,6 +44,26 @@ export const Inspector = (props: { // when quickly navigating files, which cancels update function const { notes, setNote, unCacheNote } = useInspectorStore(); + const [favorite, setFavorite] = useState(false); + + const { mutate: fileToggleFavorite, isLoading: isFavoriteLoading } = useLibraryCommand( + 'FileSetFavorite', + { + onError: () => setFavorite(!!props.selectedFile?.file?.favorite) + } + ); + + const toggleFavorite = () => { + if (!isFavoriteLoading) { + fileToggleFavorite({ id: file_id, favorite: !favorite }); + setFavorite(!favorite); + } + }; + + useEffect(() => { + setFavorite(!!props.selectedFile?.file?.favorite); + }, [props.selectedFile]); + // show cached note over server note, important to check for undefined not falsey const note = notes[file_id] === undefined ? props.selectedFile?.file?.note || null : notes[file_id]; @@ -63,95 +83,80 @@ export const Inspector = (props: { }, [note]); return ( - // -
+
{!!file_path && ( -
-
+
+
-

{file_path?.name}

-
- - - -
- {file_path?.file?.cas_id && ( - - )} - - - - - - - {!file_path?.is_dir && ( - <> - -
- {file_path?.extension && ( - - {file_path?.extension} - +
+

{file_path?.name}

+
+ + + +
+ {file_path?.file?.cas_id && ( + + )} + + + + + + + {!file_path?.is_dir && ( + <> + +
+ {file_path?.extension && ( + + {file_path?.extension} + + )} +

+ {file_path?.extension + ? //@ts-ignore + types[file_path.extension.toUpperCase()]?.descriptions.join(' / ') + : 'Unknown'} +

+
+ {file_path.file && ( + <> + + + } + /> + )} -

- {file_path?.extension - ? //@ts-ignore - types[file_path.extension.toUpperCase()]?.descriptions.join(' / ') - : 'Unknown'} -

-
- {file_path.file && ( - <> - - - } - /> - - )} - - )} - {/*
- -
*/} - {/* - */} + + )} +
)}
- // ); }; diff --git a/packages/interface/src/components/file/Sidebar.tsx b/packages/interface/src/components/file/Sidebar.tsx index d203333b1..153d755b9 100644 --- a/packages/interface/src/components/file/Sidebar.tsx +++ b/packages/interface/src/components/file/Sidebar.tsx @@ -86,13 +86,7 @@ export const Sidebar: React.FC = (props) => { let locations = Array.isArray(locationsResponse) ? locationsResponse : []; // initialize libraries - const { init: initLibraries, switchLibrary: _switchLibrary } = useLibraryStore(); - - const switchLibrary = (uuid: string) => { - navigate('overview'); - - _switchLibrary(uuid); - }; + const { init: initLibraries, switchLibrary } = useLibraryStore(); const { currentLibrary, libraries, currentLibraryUuid } = useCurrentLibrary(); @@ -163,7 +157,7 @@ export const Sidebar: React.FC = (props) => { { name: 'Library Settings', icon: CogIcon, - onPress: () => navigate('library-settings/general') + onPress: () => navigate('settings/library') }, { name: 'Add Library', @@ -192,7 +186,7 @@ export const Sidebar: React.FC = (props) => { - Content + Spaces diff --git a/packages/interface/src/components/layout/TopBar.tsx b/packages/interface/src/components/layout/TopBar.tsx index 151a880c3..beb32a322 100644 --- a/packages/interface/src/components/layout/TopBar.tsx +++ b/packages/interface/src/components/layout/TopBar.tsx @@ -1,6 +1,5 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline'; -import { useLibraryCommand } from '@sd/client'; -import { useExplorerStore } from '@sd/client'; +import { AppPropsContext, useExplorerStore, useLibraryCommand } from '@sd/client'; import { Dropdown } from '@sd/ui'; import clsx from 'clsx'; import { @@ -10,12 +9,13 @@ import { IconProps, Key, List, + Rows, + SquaresFour, Tag, TerminalWindow } from 'phosphor-react'; import React, { DetailedHTMLProps, HTMLAttributes, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; -import { AppPropsContext } from '../../AppPropsContext'; import { Shortcut } from '../primitive/Shortcut'; import { DefaultProps } from '../primitive/types'; @@ -36,12 +36,12 @@ const TopBarButton: React.FC = ({ icon: Icon, ...props }) => */} - - -
- {locations?.map((location) => ( - - ))} -
- - ); -} diff --git a/packages/interface/src/screens/settings/Settings.tsx b/packages/interface/src/screens/settings/Settings.tsx index fc903d4aa..0bcafc3f0 100644 --- a/packages/interface/src/screens/settings/Settings.tsx +++ b/packages/interface/src/screens/settings/Settings.tsx @@ -1,11 +1,28 @@ import { CogIcon, CollectionIcon, + DatabaseIcon, GlobeAltIcon, + HeartIcon, + InformationCircleIcon, KeyIcon, + LibraryIcon, + LightBulbIcon, + TagIcon, TerminalIcon } from '@heroicons/react/outline'; -import { HardDrive, PaintBrush, ShareNetwork } from 'phosphor-react'; +import { + BookOpen, + Cloud, + HardDrive, + Hash, + Info, + KeyReturn, + PaintBrush, + PuzzlePiece, + ShareNetwork, + UsersFour +} from 'phosphor-react'; import React from 'react'; import { SidebarLink } from '../../components/file/Sidebar'; @@ -23,61 +40,75 @@ export const SettingsScreen: React.FC = () => { General
+ + + Libraries + Appearance + + + Keybinds + + + + Extensions + - Node + Library + + + General + - + Nodes + + + Locations + + + + Tags + + + + Keys + + {/* + + Backups + + + + Sync + */} + Advanced - P2P + Networking - - - Libraries - - - - Security - - Developer - Experimental + Developer - {/* Library - - - My Libraries - - - - Locations - - - - Keys - - - - Tags - */} - - {/* Cloud - - - Sync - - - - Contacts - */} + Resources + + + About + + + + Changelog + + + + Support + ); }; diff --git a/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx b/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx new file mode 100644 index 000000000..01c535a13 --- /dev/null +++ b/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx @@ -0,0 +1,83 @@ +import { SearchIcon } from '@heroicons/react/solid'; +import { Button, Input } from '@sd/ui'; +import React from 'react'; + +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +// extensions should cache their logos in the app data folder +interface ExtensionItemData { + name: string; + uuid: string; + platforms: ['windows' | 'macOS' | 'linux']; + installed: boolean; + description: string; + logoUri: string; +} + +const extensions: ExtensionItemData[] = [ + { + name: 'Apple Photos', + uuid: 'com.apple.photos', + installed: true, + platforms: ['macOS'], + description: 'Import photos and videos with metadata from Apple Photos.', + logoUri: 'https://apple.com/apple-logo.png' + }, + { + name: 'Twitch VOD Archiver', + uuid: 'com.apple.photos', + installed: false, + platforms: ['macOS'], + description: 'Apple Photos is a photo management application for Mac.', + logoUri: 'https://apple.com/apple-logo.png' + }, + { + name: 'Shared Clipboard', + uuid: 'com.apple.photos', + installed: false, + platforms: ['macOS'], + description: 'Apple Photos is a photo management application for Mac.', + logoUri: 'https://apple.com/apple-logo.png' + } +]; + +function ExtensionItem(props: { extension: ExtensionItemData }) { + const { installed, name, description } = props.extension; + + return ( +
+

{name}

+

{description}

+ +
+ ); +} + +export default function ExtensionSettings() { + // const { data: volumes } = useBridgeQuery('SysGetVolumes'); + + return ( + + + + +
+ } + /> + +
+ {extensions.map((extension) => ( + + ))} +
+ + ); +} diff --git a/packages/interface/src/screens/settings/client/KeybindSettings.tsx b/packages/interface/src/screens/settings/client/KeybindSettings.tsx new file mode 100644 index 000000000..914709fbc --- /dev/null +++ b/packages/interface/src/screens/settings/client/KeybindSettings.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function AppearanceSettings() { + return ( + + + + + + + ); +} diff --git a/packages/interface/src/screens/settings/node/NodesSettings.tsx b/packages/interface/src/screens/settings/library/BackupsSettings.tsx similarity index 75% rename from packages/interface/src/screens/settings/node/NodesSettings.tsx rename to packages/interface/src/screens/settings/library/BackupsSettings.tsx index 75595f42f..56f0ab817 100644 --- a/packages/interface/src/screens/settings/node/NodesSettings.tsx +++ b/packages/interface/src/screens/settings/library/BackupsSettings.tsx @@ -6,7 +6,7 @@ import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function NodesSettings() { return ( - + ); } diff --git a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx index 87e70e4e0..9d45d2f3e 100644 --- a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx +++ b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx @@ -17,6 +17,8 @@ export default function LibraryGeneralSettings() { const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [encryptLibrary, setEncryptLibrary] = useState(false); + // prevent auto update when switching library + const [blockAutoUpdate, setBlockAutoUpdate] = useState(false); const [nameDebounced] = useDebounce(name, 500); const [descriptionDebounced] = useDebounce(description, 500); @@ -42,6 +44,18 @@ export default function LibraryGeneralSettings() { } }, [libraries]); + useEffect(() => { + if (currentLibrary) { + setBlockAutoUpdate(true); + setName(currentLibrary.config.name); + setDescription(currentLibrary.config.description); + } + }, [currentLibraryUuid]); + + useEffect(() => { + if (blockAutoUpdate) setBlockAutoUpdate(false); + }, [blockAutoUpdate]); + return (
-
- Name +
+ Name setName(e.target.value)} @@ -58,7 +72,7 @@ export default function LibraryGeneralSettings() { />
- Description + Description setDescription(e.target.value)} @@ -76,13 +90,21 @@ export default function LibraryGeneralSettings() {
+ +
+ +
+
diff --git a/packages/interface/src/screens/settings/library/NodesSettings.tsx b/packages/interface/src/screens/settings/library/NodesSettings.tsx new file mode 100644 index 000000000..d24b4a6de --- /dev/null +++ b/packages/interface/src/screens/settings/library/NodesSettings.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function NodesSettings() { + return ( + + + + ); +} diff --git a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx index 3f1b9c3a0..5c12f8c3e 100644 --- a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx +++ b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx @@ -4,6 +4,7 @@ import { useBridgeCommand, useBridgeQuery } from '@sd/client'; import { AppPropsContext } from '@sd/client'; import { LibraryConfig, LibraryConfigWrapped } from '@sd/core'; import { Button, Input } from '@sd/ui'; +import { DotsSixVertical } from 'phosphor-react'; import React, { useContext, useState } from 'react'; import Card from '../../../components/layout/Card'; @@ -26,6 +27,7 @@ function LibraryListItem(props: { library: LibraryConfigWrapped }) { return ( +

{props.library.config.name}

{props.library.uuid}

diff --git a/packages/interface/src/style.scss b/packages/interface/src/style.scss index b3e208b8d..e58d884fb 100644 --- a/packages/interface/src/style.scss +++ b/packages/interface/src/style.scss @@ -29,7 +29,7 @@ body { width: 8px; } &::-webkit-scrollbar-track { - @apply bg-[#00000006] dark:bg-[#00000030] mt-[55px] rounded-[6px]; + @apply bg-[#00000006] dark:bg-[#00000030] mt-[53px] rounded-[6px]; } &::-webkit-scrollbar-thumb { @apply rounded-[6px] bg-gray-300 dark:bg-gray-550; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4f6a891dba6a12003c55a2d11b8b936f9619afb..bb35be3d431291c12309c5aa0cbc2fd996fa1ceb 100644 GIT binary patch delta 2465 zcmaJ@Ta4RS8CEVcnNGEx8FiO4abm}g zFS!g4+=MD%A1Kjs08s=23E1+mRfPi5BDGos5>MroN(c!M`+x)mAt9j!Jek>9(W$U> zB+KXX|DW@p@BhB@4{-hQ&#u4uhs_UI_C%G$otCZ{dSB|>*4B;9SFsQrLZ)F~dD=O9 zX8O+ZnW?&U`a$94e|ayQz|Y}m2z#)5^)KE{dtyiybOPiCoyWwcv6Y}3^u-6#R@A?7 z0(Ss&V@-wCE1$a?oZSt2zPo(J6o!&%Ne~f&qag%b)ciMQfr#g~p7mRnV;H(=TkiT* z-Q2}OSSUPw_2)eJ zP?u(rNIS}kVMQ1tRUSP0xaab@_p)m4i?T1dRS1WO5P0;NQ|mA8J~IiqQvkl}+2RzL zCk2#9R*)u>ZrC;4a+>(4V@3@&Wlu5%G!N+oPPfNKwMHYcs$iH}OhS^q0b$2=B2j4& z#bVNo31r_XGbGeVM^j_4_Idxt+2f=Kl~*h`XY?><2hZD#(#fa#C`-1TtT`I;JV%ez zF>1ECVY``aF_?gxooIvD?@}lo#yc!lwIWrhk%eQ8j?;vDU4eoyN}%a1QW{hz>OANA zaZabN*s5e5WIfzzjjq#`lF{xKt!kBv*UU=1mZlQDPQAqy=vZ!_Y}TVro*c;IWH3q; ziEgIS8g`%p4q*$m%2Bt+&l+v zeQRL>y#2+6>zq=|4wRmb>w3XtJ8)O$bupL6cycUJu>uSCidY(L7CK3^W9^UQVwMuI z1c7l;G?FB>LMvO6lM#B-Y-3KD@AjO=M63_No!@(}O&@QsPs=wy2!8QP?;3! zTfx&u+gHKH*ZfyzJL|sc>)>NQ^aXZKE)EH~2XyPicelIHBZ3l*&XAF7gG#=wmvP+I zm1Y?4GNl-5;EH4#ZO)?Bc;_*l6fkV~~(0 zbrjm0KE40)^8{h*qaWEsz^`BPT$%o^x;5Q>du6(s2uvaV{Ive$g;i_Pw}tUe*Bam0 z#9e^^Prv8A492Q&?b7D_IkAr;jRD`ewg`bsulp8(_6=WP<^4d=JdU6(eTXv|AyL)I zR=1RHKlO`kG6v3T$1KV6`GBJ~xW}fQuNu!i&3~N@6#KS3NP@eEDsGv49 zYpf#imRQ_F>dZJyrzQ|q*ZTIJQ zyvyK^ulQGIy@tPWdd1dvOi5_jJAJ`ch7x%6tHo_rhbI)vvC0s!RIk$SD}{uahoZ$~UYAjJQf1>>tCtK`=)KxF z6COrbeT);q6dYkhDv9WGeVyHVX(>RTd)CHXiE)h-JX-Vaa($6vb7Uni2nBo)f#Wz? zcSLDW*r$yK9D^tpl2yJ}Y9cHlGF@h1xWwbj-DIlKtdB~43Tx@%5g(M<#9&Nn?J=uW zvkVaKcrSrBf9wr_Ki%|ffN$RMZUE+XV0mE^#$8*9eKzpY#b_wJbyV`hBUh{&n+F{| zhunW7aCQ1^ZU;R6jrYu_TyKA_pCA3db?hVm-u^S^MZHKYIl delta 141 zcmV;80CNA(y(aaSCa~lnlRyJOvri!+1GAAN=mL{U4?zXJqDVYkX<1Z4}i vL~;cB3Wvge1c$ Date: Sat, 16 Jul 2022 11:42:59 +0300 Subject: [PATCH 18/51] Use quadratic counter instead of react-countup (#333) * Fix import * Use quadratic counter instead of react-countup * Fix counter not working on launch Co-authored-by: Utku --- packages/interface/src/screens/Overview.tsx | 27 ++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/interface/src/screens/Overview.tsx b/packages/interface/src/screens/Overview.tsx index 83493c000..15378c486 100644 --- a/packages/interface/src/screens/Overview.tsx +++ b/packages/interface/src/screens/Overview.tsx @@ -5,8 +5,7 @@ import { Statistics } from '@sd/core'; import { Button, Input } from '@sd/ui'; import byteSize from 'byte-size'; import clsx from 'clsx'; -import React, { useContext, useEffect } from 'react'; -import { useCountUp } from 'react-countup'; +import React, { useContext, useEffect, useState } from 'react'; import Skeleton from 'react-loading-skeleton'; import 'react-loading-skeleton/dist/skeleton.css'; import create from 'zustand'; @@ -52,26 +51,26 @@ export const useOverviewState = create((set) => ({ })) })); +function quadratic(duration: number, range: number, current: number) { + return ((duration * 3) / Math.pow(range, 3)) * Math.pow(current, 2); +} + const StatItem: React.FC = (props) => { const { title, bytes = '0', isLoading } = props; const appProps = useContext(AppPropsContext); const size = byteSize(+bytes); - const counterRef = React.useRef(null); - const counter = useCountUp({ - end: +size.value, - ref: counterRef, - delay: 0.1, - decimals: 1, - duration: appProps?.demoMode ? 1 : 0.5, - useEasing: true - }); + const [count, setCount] = useState(0); useEffect(() => { - counter.update(+size.value); - }, [bytes]); + if (count < +size.value) { + setTimeout(() => { + setCount((count) => count + 1); + }, quadratic(appProps?.demoMode ? 1000 : 500, +size.value, count)); + } + }, [count, size]); return (
= (props) => { hidden: isLoading })} > - + {count} {size.unit}
From 26cbcae948a7083b74e05348e7b7ff67e21c0a6c Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Sun, 17 Jul 2022 19:58:21 +0800 Subject: [PATCH 19/51] fix incorrect import in Typescript --- .../interface/src/components/layout/TopBar.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/interface/src/components/layout/TopBar.tsx b/packages/interface/src/components/layout/TopBar.tsx index 151a880c3..4e527ca55 100644 --- a/packages/interface/src/components/layout/TopBar.tsx +++ b/packages/interface/src/components/layout/TopBar.tsx @@ -1,5 +1,5 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline'; -import { useLibraryCommand } from '@sd/client'; +import { AppPropsContext, useLibraryCommand } from '@sd/client'; import { useExplorerStore } from '@sd/client'; import { Dropdown } from '@sd/ui'; import clsx from 'clsx'; @@ -15,7 +15,6 @@ import { } from 'phosphor-react'; import React, { DetailedHTMLProps, HTMLAttributes, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; -import { AppPropsContext } from '../../AppPropsContext'; import { Shortcut } from '../primitive/Shortcut'; import { DefaultProps } from '../primitive/types'; @@ -51,22 +50,27 @@ const TopBarButton: React.FC = ({ icon: Icon, ...props }) => ); }; -const SearchBar: React.FC = (props) => { //TODO: maybe pass the appProps, so we can have the context in the TopBar if needed again +const SearchBar: React.FC = (props) => { + //TODO: maybe pass the appProps, so we can have the context in the TopBar if needed again const appProps = useContext(AppPropsContext); - return ( + return (
- + {/* */}
); -} +}; export const TopBar: React.FC = (props) => { const { locationId } = useExplorerStore(); From 47ae02b74322f2b53763d17c799ae5bc48d2ac40 Mon Sep 17 00:00:00 2001 From: Jamie Pine <32987599+jamiepine@users.noreply.github.com> Date: Sun, 17 Jul 2022 20:45:04 -0700 Subject: [PATCH 20/51] Bunch 'O Features (#336) * CRUD for tags. * Implement tags query in open.rs and make some changes to CRUD. * Tag update. * Hopefully working get tags. * added node config + spaces schema * add missing routes * begin tag ui * renaming query names to better fit convention * tags progress * tag edit * fix delete tag description Co-authored-by: xPolar --- apps/desktop/src-tauri/tauri.conf.json | 2 +- core/bindings/ClientQuery.ts | 2 +- core/bindings/CoreResource.ts | 3 +- core/bindings/CoreResponse.ts | 4 +- core/bindings/LibraryCommand.ts | 2 +- core/bindings/LibraryQuery.ts | 2 +- core/bindings/Tag.ts | 3 + core/bindings/TagOnFile.ts | 5 + core/bindings/TagWithFiles.ts | 5 + core/index.ts | 3 + .../20220715031021_added_spaces/migration.sql | 62 ++++++ .../20220716023638_tags_color/migration.sql | 2 + core/prisma/schema.prisma | 31 ++- core/src/file/explorer/open.rs | 29 ++- core/src/file/mod.rs | 4 +- core/src/job/worker.rs | 18 +- core/src/lib.rs | 176 ++++++++++------ core/src/library/library_manager.rs | 6 +- core/src/sys/locations.rs | 4 +- core/src/tag/mod.rs | 188 ++++++++++++++++++ packages/client/src/stores/useLibraryStore.ts | 2 +- packages/interface/package.json | 2 + packages/interface/src/App.tsx | 2 +- packages/interface/src/AppRouter.tsx | 10 +- .../src/components/file/FileList.tsx | 15 +- .../interface/src/components/file/Sidebar.tsx | 25 +-- .../src/components/jobs/RunningJobsWidget.tsx | 2 +- .../src/components/layout/TopBar.tsx | 20 +- .../components/primitive/InputContainer.tsx | 2 +- .../components/primitive/PopoverPicker.tsx | 40 ++++ .../interface/src/hooks/useClickOutside.ts | 35 ++++ packages/interface/src/screens/Debug.tsx | 8 +- packages/interface/src/screens/Explorer.tsx | 6 +- packages/interface/src/screens/Overview.tsx | 2 +- .../src/screens/settings/Settings.tsx | 19 +- .../settings/client/ExtensionsSettings.tsx | 2 +- .../settings/client/GeneralSettings.tsx | 68 +++++-- .../settings/client/PrivacySettings.tsx | 14 ++ .../screens/settings/info/AboutSpacedrive.tsx | 22 ++ .../src/screens/settings/info/Changelog.tsx | 14 ++ .../src/screens/settings/info/Support.tsx | 14 ++ .../library/LibraryGeneralSettings.tsx | 6 +- .../settings/library/LocationSettings.tsx | 2 +- .../screens/settings/library/TagsSettings.tsx | 178 ++++++++++++++++- .../settings/node/LibrariesSettings.tsx | 2 +- packages/interface/src/style.scss | 21 ++ pnpm-lock.yaml | Bin 630480 -> 630980 bytes 47 files changed, 927 insertions(+), 157 deletions(-) create mode 100644 core/bindings/Tag.ts create mode 100644 core/bindings/TagOnFile.ts create mode 100644 core/bindings/TagWithFiles.ts create mode 100644 core/prisma/migrations/20220715031021_added_spaces/migration.sql create mode 100644 core/prisma/migrations/20220716023638_tags_color/migration.sql create mode 100644 core/src/tag/mod.rs create mode 100644 packages/interface/src/components/primitive/PopoverPicker.tsx create mode 100644 packages/interface/src/hooks/useClickOutside.ts create mode 100644 packages/interface/src/screens/settings/client/PrivacySettings.tsx create mode 100644 packages/interface/src/screens/settings/info/AboutSpacedrive.tsx create mode 100644 packages/interface/src/screens/settings/info/Changelog.tsx create mode 100644 packages/interface/src/screens/settings/info/Support.tsx diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 5ebb80265..7c5bfc74c 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -63,7 +63,7 @@ "windows": [ { "title": "Spacedrive", - "width": 1200, + "width": 1400, "height": 725, "minWidth": 700, "minHeight": 500, diff --git a/core/bindings/ClientQuery.ts b/core/bindings/ClientQuery.ts index 56e37988c..d48b1859d 100644 --- a/core/bindings/ClientQuery.ts +++ b/core/bindings/ClientQuery.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { LibraryQuery } from "./LibraryQuery"; -export type ClientQuery = { key: "NodeGetLibraries" } | { key: "NodeGetState" } | { key: "SysGetVolumes" } | { key: "JobGetRunning" } | { key: "GetNodes" } | { key: "LibraryQuery", params: { library_id: string, query: LibraryQuery, } }; \ No newline at end of file +export type ClientQuery = { key: "GetLibraries" } | { key: "GetNode" } | { key: "GetVolumes" } | { key: "GetNodes" } | { key: "LibraryQuery", params: { library_id: string, query: LibraryQuery, } }; \ No newline at end of file diff --git a/core/bindings/CoreResource.ts b/core/bindings/CoreResource.ts index 4ed7811b5..7ca5999ca 100644 --- a/core/bindings/CoreResource.ts +++ b/core/bindings/CoreResource.ts @@ -2,5 +2,6 @@ import type { File } from "./File"; import type { JobReport } from "./JobReport"; import type { LocationResource } from "./LocationResource"; +import type { Tag } from "./Tag"; -export type CoreResource = "Client" | "Library" | { Location: LocationResource } | { File: File } | { Job: JobReport } | "Tag"; \ No newline at end of file +export type CoreResource = { key: "Client" } | { key: "Library" } | { key: "Location", data: LocationResource } | { key: "File", data: File } | { key: "Job", data: JobReport } | { key: "Tag", data: Tag }; \ No newline at end of file diff --git a/core/bindings/CoreResponse.ts b/core/bindings/CoreResponse.ts index cabf79dfa..f9ee981c7 100644 --- a/core/bindings/CoreResponse.ts +++ b/core/bindings/CoreResponse.ts @@ -5,6 +5,8 @@ import type { LibraryConfigWrapped } from "./LibraryConfigWrapped"; import type { LocationResource } from "./LocationResource"; import type { NodeState } from "./NodeState"; import type { Statistics } from "./Statistics"; +import type { Tag } from "./Tag"; +import type { TagWithFiles } from "./TagWithFiles"; import type { Volume } from "./Volume"; -export type CoreResponse = { key: "Success", data: null } | { key: "Error", data: string } | { key: "NodeGetLibraries", data: Array } | { key: "SysGetVolumes", data: Array } | { key: "SysGetLocation", data: LocationResource } | { key: "SysGetLocations", data: Array } | { key: "LibGetExplorerDir", data: DirectoryWithContents } | { key: "NodeGetState", data: NodeState } | { key: "LocCreate", data: LocationResource } | { key: "JobGetRunning", data: Array } | { key: "JobGetHistory", data: Array } | { key: "GetLibraryStatistics", data: Statistics }; \ No newline at end of file +export type CoreResponse = { key: "Success", data: null } | { key: "Error", data: string } | { key: "GetLibraries", data: Array } | { key: "GetVolumes", data: Array } | { key: "TagCreateResponse", data: Tag } | { key: "GetTag", data: Tag | null } | { key: "GetTags", data: Array } | { key: "GetLocation", data: LocationResource } | { key: "GetLocations", data: Array } | { key: "GetExplorerDir", data: DirectoryWithContents } | { key: "GetNode", data: NodeState } | { key: "LocCreate", data: LocationResource } | { key: "OpenTag", data: Array } | { key: "GetRunningJobs", data: Array } | { key: "GetJobHistory", data: Array } | { key: "GetLibraryStatistics", data: Statistics }; \ No newline at end of file diff --git a/core/bindings/LibraryCommand.ts b/core/bindings/LibraryCommand.ts index d99033a9f..3438a4085 100644 --- a/core/bindings/LibraryCommand.ts +++ b/core/bindings/LibraryCommand.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type LibraryCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileSetFavorite", params: { id: number, favorite: boolean, } } | { key: "FileDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { name: string, color: string, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocFullRescan", params: { id: number, } } | { key: "LocQuickRescan", params: { id: number, } } | { key: "SysVolumeUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } }; \ No newline at end of file +export type LibraryCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileSetFavorite", params: { id: number, favorite: boolean, } } | { key: "FileDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { id: number, name: string | null, color: string | null, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocFullRescan", params: { id: number, } } | { key: "LocQuickRescan", params: { id: number, } } | { key: "VolUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } }; \ No newline at end of file diff --git a/core/bindings/LibraryQuery.ts b/core/bindings/LibraryQuery.ts index 2aa14279c..d90a2fefc 100644 --- a/core/bindings/LibraryQuery.ts +++ b/core/bindings/LibraryQuery.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type LibraryQuery = { key: "LibGetTags" } | { key: "JobGetHistory" } | { key: "SysGetLocations" } | { key: "SysGetLocation", params: { id: number, } } | { key: "LibGetExplorerDir", params: { location_id: number, path: string, limit: number, } } | { key: "GetLibraryStatistics" }; \ No newline at end of file +export type LibraryQuery = { key: "GetJobHistory" } | { key: "GetLocations" } | { key: "GetLocation", params: { id: number, } } | { key: "GetRunningJobs" } | { key: "GetExplorerDir", params: { location_id: number, path: string, limit: number, } } | { key: "GetLibraryStatistics" } | { key: "GetTags" } | { key: "GetFilesTagged", params: { tag_id: number, } }; \ No newline at end of file diff --git a/core/bindings/Tag.ts b/core/bindings/Tag.ts new file mode 100644 index 000000000..60683d4b0 --- /dev/null +++ b/core/bindings/Tag.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface Tag { id: number, pub_id: string, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, } \ No newline at end of file diff --git a/core/bindings/TagOnFile.ts b/core/bindings/TagOnFile.ts new file mode 100644 index 000000000..97723b51a --- /dev/null +++ b/core/bindings/TagOnFile.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { File } from "./File"; +import type { Tag } from "./Tag"; + +export interface TagOnFile { tag_id: number, tag: Tag | null, file_id: number, file: File | null, date_created: string, } \ No newline at end of file diff --git a/core/bindings/TagWithFiles.ts b/core/bindings/TagWithFiles.ts new file mode 100644 index 000000000..6cf36acae --- /dev/null +++ b/core/bindings/TagWithFiles.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Tag } from "./Tag"; +import type { TagOnFile } from "./TagOnFile"; + +export interface TagWithFiles { tag: Tag, files_with_tag: Array, } \ No newline at end of file diff --git a/core/index.ts b/core/index.ts index 60cc1bc54..22ef6909e 100644 --- a/core/index.ts +++ b/core/index.ts @@ -24,4 +24,7 @@ export * from './bindings/NodeConfig'; export * from './bindings/NodeState'; export * from './bindings/Platform'; export * from './bindings/Statistics'; +export * from './bindings/Tag'; +export * from './bindings/TagOnFile'; +export * from './bindings/TagWithFiles'; export * from './bindings/Volume'; diff --git a/core/prisma/migrations/20220715031021_added_spaces/migration.sql b/core/prisma/migrations/20220715031021_added_spaces/migration.sql new file mode 100644 index 000000000..672e3d89d --- /dev/null +++ b/core/prisma/migrations/20220715031021_added_spaces/migration.sql @@ -0,0 +1,62 @@ +/* + Warnings: + + - You are about to drop the `label_on_files` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `tags_on_files` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropTable +PRAGMA foreign_keys=off; +DROP TABLE "label_on_files"; +PRAGMA foreign_keys=on; + +-- DropTable +PRAGMA foreign_keys=off; +DROP TABLE "tags_on_files"; +PRAGMA foreign_keys=on; + +-- CreateTable +CREATE TABLE "tags_on_file" ( + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "tag_id" INTEGER NOT NULL, + "file_id" INTEGER NOT NULL, + + PRIMARY KEY ("tag_id", "file_id"), + CONSTRAINT "tags_on_file_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "tags_on_file_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tags" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION +); + +-- CreateTable +CREATE TABLE "label_on_file" ( + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "label_id" INTEGER NOT NULL, + "file_id" INTEGER NOT NULL, + + PRIMARY KEY ("label_id", "file_id"), + CONSTRAINT "label_on_file_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "label_on_file_label_id_fkey" FOREIGN KEY ("label_id") REFERENCES "labels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION +); + +-- CreateTable +CREATE TABLE "spaces" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" TEXT NOT NULL, + "name" TEXT, + "description" TEXT, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateTable +CREATE TABLE "file_in_space" ( + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "space_id" INTEGER NOT NULL, + "file_id" INTEGER NOT NULL, + + PRIMARY KEY ("space_id", "file_id"), + CONSTRAINT "file_in_space_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "file_in_space_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "spaces" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION +); + +-- CreateIndex +CREATE UNIQUE INDEX "spaces_pub_id_key" ON "spaces"("pub_id"); diff --git a/core/prisma/migrations/20220716023638_tags_color/migration.sql b/core/prisma/migrations/20220716023638_tags_color/migration.sql new file mode 100644 index 000000000..72d3fb7b9 --- /dev/null +++ b/core/prisma/migrations/20220716023638_tags_color/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "tags" ADD COLUMN "color" TEXT; diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index 130151f62..47a0da0c1 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -133,6 +133,7 @@ model File { tags TagOnFile[] labels LabelOnFile[] albums FileInAlbum[] + spaces FileInSpace[] paths FilePath[] comments Comment[] media_data MediaData? @@ -227,6 +228,7 @@ model Tag { id Int @id @default(autoincrement()) pub_id String @unique name String? + color String? total_files Int? @default(0) redundancy_goal Int? @default(1) date_created DateTime @default(now()) @@ -246,7 +248,7 @@ model TagOnFile { file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction) @@id([tag_id, file_id]) - @@map("tags_on_files") + @@map("tags_on_file") } model Label { @@ -270,7 +272,32 @@ model LabelOnFile { file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction) @@id([label_id, file_id]) - @@map("label_on_files") + @@map("label_on_file") +} + +model Space { + id Int @id @default(autoincrement()) + pub_id String @unique + name String? + description String? + date_created DateTime @default(now()) + date_modified DateTime @default(now()) + + files FileInSpace[] + @@map("spaces") +} + +model FileInSpace { + date_created DateTime @default(now()) + + space_id Int + space Space @relation(fields: [space_id], references: [id], onDelete: NoAction, onUpdate: NoAction) + + file_id Int + file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction) + + @@id([space_id, file_id]) + @@map("file_in_space") } model Job { diff --git a/core/src/file/explorer/open.rs b/core/src/file/explorer/open.rs index 8afe1c9d8..d4e5f484d 100644 --- a/core/src/file/explorer/open.rs +++ b/core/src/file/explorer/open.rs @@ -2,8 +2,9 @@ use crate::{ encode::THUMBNAIL_CACHE_DIR_NAME, file::{DirectoryWithContents, FileError, FilePath}, library::LibraryContext, - prisma::file_path, + prisma::{file_path, tag, tag_on_file}, sys::get_location, + tag::{Tag, TagError, TagOnFile, TagWithFiles}, }; use std::path::Path; @@ -60,3 +61,29 @@ pub async fn open_dir( contents: file_paths, }) } + +pub async fn open_tag(ctx: &LibraryContext, tag_id: i32) -> Result { + let tag: Tag = ctx + .db + .tag() + .find_unique(tag::id::equals(tag_id)) + .exec() + .await? + .ok_or_else(|| TagError::TagNotFound(tag_id))? + .into(); + + let files_with_tag: Vec = ctx + .db + .tag_on_file() + .find_many(vec![tag_on_file::tag_id::equals(tag_id)]) + .exec() + .await? + .into_iter() + .map(Into::into) + .collect(); + + Ok(TagWithFiles { + tag, + files_with_tag, + }) +} diff --git a/core/src/file/mod.rs b/core/src/file/mod.rs index 780633b93..5bfa9423e 100644 --- a/core/src/file/mod.rs +++ b/core/src/file/mod.rs @@ -158,7 +158,7 @@ pub async fn set_note( ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { library_id: ctx.id.to_string(), - query: LibraryQuery::LibGetExplorerDir { + query: LibraryQuery::GetExplorerDir { limit: 0, path: "".to_string(), location_id: 0, @@ -185,7 +185,7 @@ pub async fn favorite( ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { library_id: ctx.id.to_string(), - query: LibraryQuery::LibGetExplorerDir { + query: LibraryQuery::GetExplorerDir { limit: 0, path: "".to_string(), location_id: 0, diff --git a/core/src/job/worker.rs b/core/src/job/worker.rs index 29c0ec17e..e0893e2ad 100644 --- a/core/src/job/worker.rs +++ b/core/src/job/worker.rs @@ -3,6 +3,7 @@ use super::{ Job, JobManager, }; use crate::{library::LibraryContext, ClientQuery, CoreEvent, LibraryQuery}; + use std::{sync::Arc, time::Duration}; use tokio::{ sync::{ @@ -172,7 +173,10 @@ impl Worker { } } ctx.emit(CoreEvent::InvalidateQueryDebounced( - ClientQuery::JobGetRunning, + ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::GetRunningJobs, + }, )) .await; } @@ -180,12 +184,15 @@ impl Worker { worker.job_report.status = JobStatus::Completed; worker.job_report.update(&ctx).await.unwrap_or(()); - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::JobGetRunning)) - .await; + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::GetRunningJobs, + })) + .await; ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { library_id: ctx.id.to_string(), - query: LibraryQuery::JobGetHistory, + query: LibraryQuery::GetJobHistory, })) .await; break; @@ -196,9 +203,10 @@ impl Worker { ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { library_id: ctx.id.to_string(), - query: LibraryQuery::JobGetHistory, + query: LibraryQuery::GetJobHistory, })) .await; + break; } } diff --git a/core/src/lib.rs b/core/src/lib.rs index f865b766b..29dfecd2e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -8,6 +8,8 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use tag::{Tag, TagWithFiles}; + use thiserror::Error; use tokio::sync::{ mpsc::{self, unbounded_channel, UnboundedReceiver, UnboundedSender}, @@ -24,6 +26,7 @@ mod library; mod node; mod prisma; mod sys; +mod tag; mod util; // a wrapper around external input with a returning sender channel for core to respond @@ -235,15 +238,18 @@ impl Node { CoreResponse::Success(()) } // CRUD for tags - LibraryCommand::TagCreate { name: _, color: _ } => todo!(), - LibraryCommand::TagAssign { - file_id: _, - tag_id: _, - } => todo!(), - LibraryCommand::TagUpdate { name: _, color: _ } => todo!(), - LibraryCommand::TagDelete { id: _ } => todo!(), + LibraryCommand::TagCreate { name, color } => { + tag::create_tag(ctx, name, color).await? + } + LibraryCommand::TagAssign { file_id, tag_id } => { + tag::tag_assign(ctx, file_id, tag_id).await? + } + LibraryCommand::TagDelete { id } => tag::tag_delete(ctx, id).await?, + LibraryCommand::TagUpdate { id, name, color } => { + tag::update_tag(ctx, id, name, color).await? + } // CRUD for libraries - LibraryCommand::SysVolumeUnmount { id: _ } => todo!(), + LibraryCommand::VolUnmount { id: _ } => todo!(), LibraryCommand::GenerateThumbsForLocation { id, path } => { ctx.spawn_job(Box::new(ThumbnailJob { location_id: id, @@ -269,18 +275,15 @@ impl Node { // query sources of data async fn exec_query(&self, query: ClientQuery) -> Result { Ok(match query { - ClientQuery::NodeGetLibraries => CoreResponse::NodeGetLibraries( - self.library_manager.get_all_libraries_config().await, - ), - ClientQuery::NodeGetState => CoreResponse::NodeGetState(NodeState { + ClientQuery::GetLibraries => { + CoreResponse::GetLibraries(self.library_manager.get_all_libraries_config().await) + } + ClientQuery::GetNode => CoreResponse::GetNode(NodeState { config: self.config.get().await, data_path: self.config.data_directory().to_str().unwrap().to_string(), }), - ClientQuery::SysGetVolumes => CoreResponse::SysGetVolumes(sys::Volume::get_volumes()?), - ClientQuery::JobGetRunning => { - CoreResponse::JobGetRunning(self.jobs.get_running().await) - } ClientQuery::GetNodes => todo!(), + ClientQuery::GetVolumes => CoreResponse::GetVolumes(sys::Volume::get_volumes()?), ClientQuery::LibraryQuery { library_id, query } => { let ctx = match self.library_manager.get_ctx(library_id.clone()).await { Some(ctx) => ctx, @@ -290,28 +293,34 @@ impl Node { } }; match query { - LibraryQuery::SysGetLocations => { - CoreResponse::SysGetLocations(sys::get_locations(&ctx).await?) + LibraryQuery::GetLocations => { + CoreResponse::GetLocations(sys::get_locations(&ctx).await?) + } + LibraryQuery::GetRunningJobs => { + CoreResponse::GetRunningJobs(self.jobs.get_running().await) } // get location from library - LibraryQuery::SysGetLocation { id } => { - CoreResponse::SysGetLocation(sys::get_location(&ctx, id).await?) + LibraryQuery::GetLocation { id } => { + CoreResponse::GetLocation(sys::get_location(&ctx, id).await?) } // return contents of a directory for the explorer - LibraryQuery::LibGetExplorerDir { + LibraryQuery::GetExplorerDir { path, location_id, limit: _, - } => CoreResponse::LibGetExplorerDir( + } => CoreResponse::GetExplorerDir( file::explorer::open_dir(&ctx, &location_id, &path).await?, ), - LibraryQuery::LibGetTags => todo!(), - LibraryQuery::JobGetHistory => { - CoreResponse::JobGetHistory(JobManager::get_history(&ctx).await?) + LibraryQuery::GetJobHistory => { + CoreResponse::GetJobHistory(JobManager::get_history(&ctx).await?) } LibraryQuery::GetLibraryStatistics => CoreResponse::GetLibraryStatistics( library::Statistics::calculate(&ctx).await?, ), + LibraryQuery::GetTags => tag::get_all_tags(ctx).await?, + LibraryQuery::GetFilesTagged { tag_id } => { + tag::get_files_for_tag(ctx, tag_id).await? + } } } }) @@ -347,27 +356,68 @@ pub enum ClientCommand { #[ts(export)] pub enum LibraryCommand { // Files - FileReadMetaData { id: i32 }, - FileSetNote { id: i32, note: Option }, - FileSetFavorite { id: i32, favorite: bool }, + FileReadMetaData { + id: i32, + }, + FileSetNote { + id: i32, + note: Option, + }, + FileSetFavorite { + id: i32, + favorite: bool, + }, // FileEncrypt { id: i32, algorithm: EncryptionAlgorithm }, - FileDelete { id: i32 }, + FileDelete { + id: i32, + }, // Tags - TagCreate { name: String, color: String }, - TagUpdate { name: String, color: String }, - TagAssign { file_id: i32, tag_id: i32 }, - TagDelete { id: i32 }, + TagCreate { + name: String, + color: String, + }, + TagUpdate { + id: i32, + name: Option, + color: Option, + }, + TagAssign { + file_id: i32, + tag_id: i32, + }, + TagDelete { + id: i32, + }, // Locations - LocCreate { path: String }, - LocUpdate { id: i32, name: Option }, - LocDelete { id: i32 }, - LocFullRescan { id: i32 }, - LocQuickRescan { id: i32 }, + LocCreate { + path: String, + }, + LocUpdate { + id: i32, + name: Option, + }, + LocDelete { + id: i32, + }, + LocFullRescan { + id: i32, + }, + LocQuickRescan { + id: i32, + }, // System - SysVolumeUnmount { id: i32 }, - GenerateThumbsForLocation { id: i32, path: String }, + VolUnmount { + id: i32, + }, + GenerateThumbsForLocation { + id: i32, + path: String, + }, // PurgeDatabase, - IdentifyUniqueFiles { id: i32, path: String }, + IdentifyUniqueFiles { + id: i32, + path: String, + }, } /// is a query destined for the core @@ -375,10 +425,9 @@ pub enum LibraryCommand { #[serde(tag = "key", content = "params")] #[ts(export)] pub enum ClientQuery { - NodeGetLibraries, - NodeGetState, - SysGetVolumes, - JobGetRunning, + GetLibraries, + GetNode, + GetVolumes, GetNodes, LibraryQuery { library_id: String, @@ -391,18 +440,22 @@ pub enum ClientQuery { #[serde(tag = "key", content = "params")] #[ts(export)] pub enum LibraryQuery { - LibGetTags, - JobGetHistory, - SysGetLocations, - SysGetLocation { + GetJobHistory, + GetLocations, + GetLocation { id: i32, }, - LibGetExplorerDir { + GetRunningJobs, + GetExplorerDir { location_id: i32, path: String, limit: i32, }, GetLibraryStatistics, + GetTags, + GetFilesTagged { + tag_id: i32, + }, } // represents an event this library can emit @@ -433,15 +486,19 @@ pub struct NodeState { pub enum CoreResponse { Success(()), Error(String), - NodeGetLibraries(Vec), - SysGetVolumes(Vec), - SysGetLocation(sys::LocationResource), - SysGetLocations(Vec), - LibGetExplorerDir(file::DirectoryWithContents), - NodeGetState(NodeState), + GetLibraries(Vec), + GetVolumes(Vec), + TagCreateResponse(tag::Tag), + GetTag(Option), + GetTags(Vec), + GetLocation(sys::LocationResource), + GetLocations(Vec), + GetExplorerDir(file::DirectoryWithContents), + GetNode(NodeState), LocCreate(sys::LocationResource), - JobGetRunning(Vec), - JobGetHistory(Vec), + OpenTag(Vec), + GetRunningJobs(Vec), + GetJobHistory(Vec), GetLibraryStatistics(library::Statistics), } @@ -462,6 +519,7 @@ pub enum CoreError { } #[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[serde(tag = "key", content = "data")] #[ts(export)] pub enum CoreResource { Client, @@ -469,5 +527,5 @@ pub enum CoreResource { Location(sys::LocationResource), File(file::File), Job(JobReport), - Tag, + Tag(tag::Tag), } diff --git a/core/src/library/library_manager.rs b/core/src/library/library_manager.rs index 5c1aca635..45e9a2acd 100644 --- a/core/src/library/library_manager.rs +++ b/core/src/library/library_manager.rs @@ -138,7 +138,7 @@ impl LibraryManager { self.libraries.write().await.push(library); self.node_context - .emit(CoreEvent::InvalidateQuery(ClientQuery::NodeGetLibraries)) + .emit(CoreEvent::InvalidateQuery(ClientQuery::GetLibraries)) .await; Ok(()) @@ -184,7 +184,7 @@ impl LibraryManager { .await?; self.node_context - .emit(CoreEvent::InvalidateQuery(ClientQuery::NodeGetLibraries)) + .emit(CoreEvent::InvalidateQuery(ClientQuery::GetLibraries)) .await; Ok(()) } @@ -209,7 +209,7 @@ impl LibraryManager { libraries.retain(|l| l.id != id); self.node_context - .emit(CoreEvent::InvalidateQuery(ClientQuery::NodeGetLibraries)) + .emit(CoreEvent::InvalidateQuery(ClientQuery::GetLibraries)) .await; Ok(()) } diff --git a/core/src/sys/locations.rs b/core/src/sys/locations.rs index 220bacecb..13ed06fbb 100644 --- a/core/src/sys/locations.rs +++ b/core/src/sys/locations.rs @@ -212,7 +212,7 @@ pub async fn create_location( Err(e) => Err(LocationError::DotfileWriteFailure(e, path.to_string()))?, } - // ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::SysGetLocations)) + // ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::GetLocations)) // .await; location @@ -239,7 +239,7 @@ pub async fn delete_location(ctx: &LibraryContext, location_id: i32) -> Result<( ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { library_id: ctx.id.to_string(), - query: LibraryQuery::SysGetLocations, + query: LibraryQuery::GetLocations, })) .await; diff --git a/core/src/tag/mod.rs b/core/src/tag/mod.rs new file mode 100644 index 000000000..61d343333 --- /dev/null +++ b/core/src/tag/mod.rs @@ -0,0 +1,188 @@ +use crate::{ + file::File, + library::LibraryContext, + prisma::{ + self, file, + tag::{self}, + tag_on_file, + }, + ClientQuery, CoreError, CoreEvent, CoreResponse, LibraryQuery, +}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use ts_rs::TS; + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct Tag { + pub id: i32, + pub pub_id: String, + pub name: Option, + pub color: Option, + + pub total_files: Option, + pub redundancy_goal: Option, + + pub date_created: chrono::DateTime, + pub date_modified: chrono::DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct TagOnFile { + pub tag_id: i32, + pub tag: Option, + + pub file_id: i32, + pub file: Option, + + pub date_created: chrono::DateTime, +} + +impl Into for tag::Data { + fn into(self) -> Tag { + Tag { + id: self.id, + pub_id: self.pub_id, + name: self.name, + color: self.color, + total_files: self.total_files, + redundancy_goal: self.redundancy_goal, + date_created: self.date_created.into(), + date_modified: self.date_modified.into(), + } + } +} + +impl Into for tag_on_file::Data { + fn into(self) -> TagOnFile { + TagOnFile { + tag_id: self.tag_id, + tag: self.tag.map(|t| (*t).into()), + file_id: self.file_id, + file: self.file.map(|f| (*f).into()), + date_created: self.date_created.into(), + } + } +} + +#[derive(Serialize, Deserialize, TS, Debug)] +#[ts(export)] +pub struct TagWithFiles { + pub tag: Tag, + pub files_with_tag: Vec, +} + +#[derive(Error, Debug)] +pub enum TagError { + #[error("Tag not found")] + TagNotFound(i32), + #[error("Database error")] + DatabaseError(#[from] prisma::QueryError), +} + +pub async fn create_tag( + ctx: LibraryContext, + name: String, + color: String, +) -> Result { + let created_tag = ctx + .db + .tag() + .create( + tag::pub_id::set(uuid::Uuid::new_v4().to_string()), + vec![tag::name::set(Some(name)), tag::color::set(Some(color))], + ) + .exec() + .await + .unwrap(); + + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::GetTags, + })) + .await; + + Ok(CoreResponse::TagCreateResponse(created_tag.into())) +} + +pub async fn update_tag( + ctx: LibraryContext, + id: i32, + name: Option, + color: Option, +) -> Result { + ctx.db + .tag() + .find_unique(tag::id::equals(id)) + .update(vec![tag::name::set(name), tag::color::set(color)]) + .exec() + .await + .unwrap(); + + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::GetTags, + })) + .await; + + Ok(CoreResponse::Success(())) +} + +pub async fn tag_assign( + ctx: LibraryContext, + file_id: i32, + tag_id: i32, +) -> Result { + ctx.db.tag_on_file().create( + tag_on_file::tag::link(tag::UniqueWhereParam::IdEquals(tag_id)), + tag_on_file::file::link(file::UniqueWhereParam::IdEquals(file_id)), + vec![], + ); + + Ok(CoreResponse::Success(())) +} + +pub async fn tag_delete(ctx: LibraryContext, id: i32) -> Result { + ctx.db + .tag() + .find_unique(tag::id::equals(id)) + .delete() + .exec() + .await? + .unwrap(); + + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id.to_string(), + query: LibraryQuery::GetTags, + })) + .await; + + Ok(CoreResponse::Success(())) +} + +pub async fn get_files_for_tag(ctx: LibraryContext, id: i32) -> Result { + let tag: Option = ctx + .db + .tag() + .find_unique(tag::id::equals(id)) + .exec() + .await? + .map(Into::into); + + Ok(CoreResponse::GetTag(tag)) +} + +pub async fn get_all_tags(ctx: LibraryContext) -> Result { + let tags: Vec = ctx + .db + .tag() + .find_many(vec![]) + .exec() + .await? + .into_iter() + .map(Into::into) + .collect(); + + Ok(CoreResponse::GetTags(tags)) +} diff --git a/packages/client/src/stores/useLibraryStore.ts b/packages/client/src/stores/useLibraryStore.ts index 53a8a4e55..fecbf871d 100644 --- a/packages/client/src/stores/useLibraryStore.ts +++ b/packages/client/src/stores/useLibraryStore.ts @@ -51,7 +51,7 @@ export const useLibraryStore = create()( // is memorized and can be used safely in any component export const useCurrentLibrary = () => { const { currentLibraryUuid, switchLibrary } = useLibraryStore(); - const { data: libraries } = useBridgeQuery('NodeGetLibraries', undefined, {}); + const { data: libraries } = useBridgeQuery('GetLibraries', undefined, {}); // memorize library to avoid re-running find function const currentLibrary = useMemo(() => { diff --git a/packages/interface/package.json b/packages/interface/package.json index 02e05a74d..709290266 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -38,10 +38,12 @@ "phosphor-react": "^1.4.1", "pretty-bytes": "^6.0.0", "react": "^18.1.0", + "react-colorful": "^5.5.1", "react-countup": "^6.2.0", "react-dom": "^18.1.0", "react-dropzone": "^14.2.1", "react-error-boundary": "^3.1.4", + "react-hook-form": "^7.31.3", "react-hotkeys-hook": "^3.4.6", "react-json-view": "^1.21.3", "react-loading-icons": "^1.1.0", diff --git a/packages/interface/src/App.tsx b/packages/interface/src/App.tsx index a04b4382f..c9f672640 100644 --- a/packages/interface/src/App.tsx +++ b/packages/interface/src/App.tsx @@ -16,7 +16,7 @@ const queryClient = new QueryClient(); function RouterContainer(props: { props: AppProps }) { useCoreEvents(); const [appProps, setAppProps] = useState(props.props); - const { data: client } = useBridgeQuery('NodeGetState'); + const { data: client } = useBridgeQuery('GetNode'); useEffect(() => { setAppProps({ diff --git a/packages/interface/src/AppRouter.tsx b/packages/interface/src/AppRouter.tsx index 640d3f1eb..644864922 100644 --- a/packages/interface/src/AppRouter.tsx +++ b/packages/interface/src/AppRouter.tsx @@ -17,6 +17,10 @@ import AppearanceSettings from './screens/settings/client/AppearanceSettings'; import ExtensionSettings from './screens/settings/client/ExtensionsSettings'; import GeneralSettings from './screens/settings/client/GeneralSettings'; import KeybindSettings from './screens/settings/client/KeybindSettings'; +import PrivacySettings from './screens/settings/client/PrivacySettings'; +import AboutSpacedrive from './screens/settings/info/AboutSpacedrive'; +import Changelog from './screens/settings/info/Changelog'; +import Support from './screens/settings/info/Support'; import ContactsSettings from './screens/settings/library/ContactsSettings'; import KeysSettings from './screens/settings/library/KeysSetting'; import LibraryGeneralSettings from './screens/settings/library/LibraryGeneralSettings'; @@ -34,7 +38,7 @@ export function AppRouter() { let location = useLocation(); let state = location.state as { backgroundLocation?: Location }; const libraryState = useLibraryStore(); - const { data: libraries } = useBridgeQuery('NodeGetLibraries'); + const { data: libraries } = useBridgeQuery('GetLibraries'); // TODO: This can be removed once we add a setup flow to the app useEffect(() => { @@ -79,6 +83,10 @@ export function AppRouter() { } /> } /> } /> + } /> + } /> + } /> + } /> } /> } /> diff --git a/packages/interface/src/components/file/FileList.tsx b/packages/interface/src/components/file/FileList.tsx index e2a792a60..403c6afe8 100644 --- a/packages/interface/src/components/file/FileList.tsx +++ b/packages/interface/src/components/file/FileList.tsx @@ -21,7 +21,7 @@ interface IColumn { const PADDING_SIZE = 130; -// Function ensure no types are loss, but guarantees that they are Column[] +// Function ensure no types are lost, but guarantees that they are Column[] function ensureIsColumns(data: T) { return data; } @@ -48,20 +48,19 @@ const GridItemContainer = styled.div` `; export const FileList: React.FC<{ location_id: number; path: string; limit: number }> = (props) => { + const path = props.path; const size = useWindowSize(); const tableContainer = useRef(null); const VList = useRef(null); - const { data: client } = useBridgeQuery('NodeGetState', undefined, { + const { data: client } = useBridgeQuery('GetNode', undefined, { refetchOnWindowFocus: false }); - const path = props.path; - const { selectedRowIndex, setSelectedRowIndex, setLocationId, layoutMode } = useExplorerStore(); const [goingUp, setGoingUp] = useState(false); - const { data: currentDir } = useLibraryQuery('LibGetExplorerDir', { + const { data: currentDir } = useLibraryQuery('GetExplorerDir', { location_id: props.location_id, path, limit: props.limit @@ -124,11 +123,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb ); return ( -
+
diff --git a/packages/interface/src/components/file/Sidebar.tsx b/packages/interface/src/components/file/Sidebar.tsx index 153d755b9..ca4052c0e 100644 --- a/packages/interface/src/components/file/Sidebar.tsx +++ b/packages/interface/src/components/file/Sidebar.tsx @@ -22,7 +22,7 @@ export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) {({ isActive }) => ( = (props) => { const appProps = useContext(AppPropsContext); - const { data: locationsResponse, isError: isLocationsError } = useLibraryQuery('SysGetLocations'); + const { data: locationsResponse, isError: isLocationsError } = useLibraryQuery('GetLocations'); let locations = Array.isArray(locationsResponse) ? locationsResponse : []; @@ -96,13 +96,7 @@ export const Sidebar: React.FC = (props) => { const { mutate: createLocation } = useLibraryCommand('LocCreate'); - const tags = [ - { id: 1, name: 'Keepsafe', color: '#FF6788' }, - { id: 2, name: 'OBS', color: '#BF88FF' }, - { id: 3, name: 'BlackMagic', color: '#F0C94A' }, - { id: 4, name: 'Camera Roll', color: '#00F0DB' }, - { id: 5, name: 'Spacedrive', color: '#00F079' } - ]; + const { data: tags } = useLibraryQuery('GetTags'); return (
= (props) => { Photos - - {isExperimental ? ( - - - Debug - - ) : ( - <> - )}
Locations @@ -256,11 +241,11 @@ export const Sidebar: React.FC = (props) => {
Tags
- {tags.map((tag, index) => ( + {tags?.slice(0, 6).map((tag, index) => (
{tag.name} diff --git a/packages/interface/src/components/jobs/RunningJobsWidget.tsx b/packages/interface/src/components/jobs/RunningJobsWidget.tsx index 85152f4f0..0ef77eccf 100644 --- a/packages/interface/src/components/jobs/RunningJobsWidget.tsx +++ b/packages/interface/src/components/jobs/RunningJobsWidget.tsx @@ -51,7 +51,7 @@ const MiddleTruncatedText = ({ }; export default function RunningJobsWidget() { - const { data: jobs } = useBridgeQuery('JobGetRunning'); + const { data: jobs } = useBridgeQuery('GetRunningJobs'); return (
diff --git a/packages/interface/src/components/layout/TopBar.tsx b/packages/interface/src/components/layout/TopBar.tsx index beb32a322..d5df60bd1 100644 --- a/packages/interface/src/components/layout/TopBar.tsx +++ b/packages/interface/src/components/layout/TopBar.tsx @@ -31,19 +31,27 @@ export interface TopBarButtonProps } interface SearchBarProps extends DefaultProps {} -const TopBarButton: React.FC = ({ icon: Icon, ...props }) => { +const TopBarButton: React.FC = ({ + icon: Icon, + left, + right, + group, + active, + className, + ...props +}) => { return (
diff --git a/packages/interface/src/components/primitive/PopoverPicker.tsx b/packages/interface/src/components/primitive/PopoverPicker.tsx new file mode 100644 index 000000000..7ba176126 --- /dev/null +++ b/packages/interface/src/components/primitive/PopoverPicker.tsx @@ -0,0 +1,40 @@ +import clsx from 'clsx'; +import React, { useCallback, useRef, useState } from 'react'; +import { HexColorPicker } from 'react-colorful'; + +import useClickOutside from '../../hooks/useClickOutside'; + +interface PopoverPickerProps { + value: string; + onChange: (color: string) => void; + className?: string; +} + +export const PopoverPicker = ({ value, onChange, className }: PopoverPickerProps) => { + const popover = useRef(null); + const [isOpen, toggle] = useState(false); + + const close = useCallback(() => toggle(false), []); + useClickOutside(popover, close); + + return ( +
+
toggle(true)} + /> + {/* Pick Color */} + + {isOpen && ( +
+ +
+ )} +
+ ); +}; diff --git a/packages/interface/src/hooks/useClickOutside.ts b/packages/interface/src/hooks/useClickOutside.ts new file mode 100644 index 000000000..96aa121f4 --- /dev/null +++ b/packages/interface/src/hooks/useClickOutside.ts @@ -0,0 +1,35 @@ +import { useEffect } from 'react'; + +// Improved version of https://usehooks.com/useOnClickOutside/ +const useClickOutside = (ref: any, handler: any) => { + useEffect(() => { + let startedInside = false; + let startedWhenMounted = false; + + const listener = (event: any) => { + // Do nothing if `mousedown` or `touchstart` started inside ref element + if (startedInside || !startedWhenMounted) return; + // Do nothing if clicking ref's element or descendent elements + if (!ref.current || ref.current.contains(event.target)) return; + + handler(event); + }; + + const validateEventStart = (event: any) => { + startedWhenMounted = ref.current; + startedInside = ref.current && ref.current.contains(event.target); + }; + + document.addEventListener('mousedown', validateEventStart); + document.addEventListener('touchstart', validateEventStart); + document.addEventListener('click', listener); + + return () => { + document.removeEventListener('mousedown', validateEventStart); + document.removeEventListener('touchstart', validateEventStart); + document.removeEventListener('click', listener); + }; + }, [ref, handler]); +}; + +export default useClickOutside; diff --git a/packages/interface/src/screens/Debug.tsx b/packages/interface/src/screens/Debug.tsx index d0758facb..254a1b90a 100644 --- a/packages/interface/src/screens/Debug.tsx +++ b/packages/interface/src/screens/Debug.tsx @@ -7,10 +7,10 @@ import CodeBlock from '../components/primitive/Codeblock'; export const DebugScreen: React.FC<{}> = (props) => { const appPropsContext = useContext(AppPropsContext); - const { data: nodeState } = useBridgeQuery('NodeGetState'); - const { data: libraryState } = useBridgeQuery('NodeGetLibraries'); - const { data: jobs } = useBridgeQuery('JobGetRunning'); - const { data: jobHistory } = useLibraryQuery('JobGetHistory'); + const { data: nodeState } = useBridgeQuery('GetNode'); + const { data: libraryState } = useBridgeQuery('GetLibraries'); + const { data: jobs } = useBridgeQuery('GetRunningJobs'); + const { data: jobHistory } = useLibraryQuery('GetJobHistory'); // const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', { // onMutate: () => { // alert('Database purged'); diff --git a/packages/interface/src/screens/Explorer.tsx b/packages/interface/src/screens/Explorer.tsx index 5ef7f4025..d0db3468d 100644 --- a/packages/interface/src/screens/Explorer.tsx +++ b/packages/interface/src/screens/Explorer.tsx @@ -19,17 +19,17 @@ export const ExplorerScreen: React.FC<{}> = () => { const { selectedRowIndex } = useExplorerStore(); // Current Location - const { data: currentLocation } = useLibraryQuery('SysGetLocation', { id: location_id }); + const { data: currentLocation } = useLibraryQuery('GetLocation', { id: location_id }); // Current Directory const { data: currentDir } = useLibraryQuery( - 'LibGetExplorerDir', + 'GetExplorerDir', { location_id: location_id!, path, limit }, { enabled: !!location_id } ); return ( -
+
diff --git a/packages/interface/src/screens/Overview.tsx b/packages/interface/src/screens/Overview.tsx index 15378c486..d065d3673 100644 --- a/packages/interface/src/screens/Overview.tsx +++ b/packages/interface/src/screens/Overview.tsx @@ -102,7 +102,7 @@ const StatItem: React.FC = (props) => { export const OverviewScreen = () => { const { data: libraryStatistics, isLoading: isStatisticsLoading } = useLibraryQuery('GetLibraryStatistics'); - const { data: nodeState } = useBridgeQuery('NodeGetState'); + const { data: nodeState } = useBridgeQuery('GetNode'); const { overviewStats, setOverviewStats } = useOverviewState(); diff --git a/packages/interface/src/screens/settings/Settings.tsx b/packages/interface/src/screens/settings/Settings.tsx index 0bcafc3f0..6a5aaec6d 100644 --- a/packages/interface/src/screens/settings/Settings.tsx +++ b/packages/interface/src/screens/settings/Settings.tsx @@ -1,4 +1,5 @@ import { + AnnotationIcon, CogIcon, CollectionIcon, DatabaseIcon, @@ -8,12 +9,17 @@ import { KeyIcon, LibraryIcon, LightBulbIcon, + LockClosedIcon, + ShieldCheckIcon, + SparklesIcon, TagIcon, TerminalIcon } from '@heroicons/react/outline'; import { BookOpen, Cloud, + FlyingSaucer, + HandWaving, HardDrive, Hash, Info, @@ -21,6 +27,7 @@ import { PaintBrush, PuzzlePiece, ShareNetwork, + Shield, UsersFour } from 'phosphor-react'; import React from 'react'; @@ -44,6 +51,10 @@ export const SettingsScreen: React.FC = () => { Libraries + + + Privacy + Appearance @@ -86,7 +97,7 @@ export const SettingsScreen: React.FC = () => { Sync */} - Advanced + {/* Advanced Networking @@ -94,15 +105,15 @@ export const SettingsScreen: React.FC = () => { Developer - + */} Resources - + About - + Changelog diff --git a/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx b/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx index 01c535a13..9a2645e37 100644 --- a/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx +++ b/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx @@ -58,7 +58,7 @@ function ExtensionItem(props: { extension: ExtensionItemData }) { } export default function ExtensionSettings() { - // const { data: volumes } = useBridgeQuery('SysGetVolumes'); + // const { data: volumes } = useBridgeQuery('GetVolumes'); return ( diff --git a/packages/interface/src/screens/settings/client/GeneralSettings.tsx b/packages/interface/src/screens/settings/client/GeneralSettings.tsx index 66550f7c8..3c42dda37 100644 --- a/packages/interface/src/screens/settings/client/GeneralSettings.tsx +++ b/packages/interface/src/screens/settings/client/GeneralSettings.tsx @@ -1,10 +1,15 @@ +import { useBridgeQuery } from '@sd/client'; +import { Input } from '@sd/ui'; +import { Database } from 'phosphor-react'; import React from 'react'; +import Card from '../../../components/layout/Card'; +import { Toggle } from '../../../components/primitive'; import { SettingsContainer } from '../../../components/settings/SettingsContainer'; import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function GeneralSettings() { - // const { data: volumes } = useBridgeQuery('SysGetVolumes'); + const { data: node } = useBridgeQuery('GetNode'); return ( @@ -12,24 +17,53 @@ export default function GeneralSettings() { title="General Settings" description="General settings related to this client." /> - {/* -
-
- { - const name = volume.name && volume.name.length ? volume.name : volume.mount_point; - return { - key: name, - option: name, - description: volume.mount_point - }; - }) ?? [] - } - /> + +
+
+ Connected Node +
+
+ 0 Peers + + Running + +
+
+ +
+
+
+ + Node Name + + +
+
+ + Node Port + + +
+
+ + Node ID + + +
+
+
+ + Run daemon when app closed +
+
+ + + Data Folder + {node?.data_path} +
- */} + ); } diff --git a/packages/interface/src/screens/settings/client/PrivacySettings.tsx b/packages/interface/src/screens/settings/client/PrivacySettings.tsx new file mode 100644 index 000000000..b2e1e6475 --- /dev/null +++ b/packages/interface/src/screens/settings/client/PrivacySettings.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function PrivacySettings() { + return ( + + + + ); +} diff --git a/packages/interface/src/screens/settings/info/AboutSpacedrive.tsx b/packages/interface/src/screens/settings/info/AboutSpacedrive.tsx new file mode 100644 index 000000000..209b10705 --- /dev/null +++ b/packages/interface/src/screens/settings/info/AboutSpacedrive.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function AboutSpacedrive() { + return ( + + + Version {} +
+ Created by + + Jamie Pine, Brendan Allan, Oscar Beaumont, Haden Fletcher, Haris Mehrzad Benjamin Akar, + and many more. + +
+
+ ); +} diff --git a/packages/interface/src/screens/settings/info/Changelog.tsx b/packages/interface/src/screens/settings/info/Changelog.tsx new file mode 100644 index 000000000..be5375016 --- /dev/null +++ b/packages/interface/src/screens/settings/info/Changelog.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function Changelog() { + return ( + + + + ); +} diff --git a/packages/interface/src/screens/settings/info/Support.tsx b/packages/interface/src/screens/settings/info/Support.tsx new file mode 100644 index 000000000..bff11a308 --- /dev/null +++ b/packages/interface/src/screens/settings/info/Support.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { SettingsContainer } from '../../../components/settings/SettingsContainer'; +import { SettingsHeader } from '../../../components/settings/SettingsHeader'; + +export default function Support() { + return ( + + + + ); +} diff --git a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx index 9d45d2f3e..917364378 100644 --- a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx +++ b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx @@ -64,7 +64,7 @@ export default function LibraryGeneralSettings() { />
- Name + Name setName(e.target.value)} @@ -72,7 +72,9 @@ export default function LibraryGeneralSettings() { />
- Description + + Description + setDescription(e.target.value)} diff --git a/packages/interface/src/screens/settings/library/LocationSettings.tsx b/packages/interface/src/screens/settings/library/LocationSettings.tsx index 3b6c67c26..ed1a131f6 100644 --- a/packages/interface/src/screens/settings/library/LocationSettings.tsx +++ b/packages/interface/src/screens/settings/library/LocationSettings.tsx @@ -16,7 +16,7 @@ import { SettingsHeader } from '../../../components/settings/SettingsHeader'; // ]; export default function LocationSettings() { - const { data: locations } = useLibraryQuery('SysGetLocations'); + const { data: locations } = useLibraryQuery('GetLocations'); const appProps = useContext(AppPropsContext); diff --git a/packages/interface/src/screens/settings/library/TagsSettings.tsx b/packages/interface/src/screens/settings/library/TagsSettings.tsx index 19bb977f6..ce7eaf9ba 100644 --- a/packages/interface/src/screens/settings/library/TagsSettings.tsx +++ b/packages/interface/src/screens/settings/library/TagsSettings.tsx @@ -1,12 +1,186 @@ -import React from 'react'; +import { TrashIcon } from '@heroicons/react/outline'; +import { useLibraryCommand, useLibraryQuery } from '@sd/client'; +import { Button, Input } from '@sd/ui'; +import clsx from 'clsx'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { useDebounce } from 'rooks'; +import Card from '../../../components/layout/Card'; +import Dialog from '../../../components/layout/Dialog'; +import { Toggle } from '../../../components/primitive'; +import { InputContainer } from '../../../components/primitive/InputContainer'; +import { PopoverPicker } from '../../../components/primitive/PopoverPicker'; import { SettingsContainer } from '../../../components/settings/SettingsContainer'; import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function TagsSettings() { + const [openCreateModal, setOpenCreateModal] = useState(false); + // creating new tag state + const [newColor, setNewColor] = useState('#A717D9'); + const [newName, setNewName] = useState(''); + + const { data: tags } = useLibraryQuery('GetTags'); + + const [selectedTag, setSelectedTag] = useState(null); + + const currentTag = useMemo(() => { + return tags?.find((t) => t.id === selectedTag); + }, [tags, selectedTag]); + + const { mutate: createTag, isLoading } = useLibraryCommand('TagCreate', { + onError: (e) => { + console.log('error', e); + }, + onSuccess: (data) => { + setOpenCreateModal(false); + } + }); + + const { mutate: updateTag, isLoading: tagUpdateLoading } = useLibraryCommand('TagUpdate'); + + const { mutate: deleteTag, isLoading: tagDeleteLoading } = useLibraryCommand('TagDelete'); + + // set default selected tag + useEffect(() => { + if (!currentTag && tags?.length) { + setSelectedTag(tags[0].id); + } + }, [tags]); + + useEffect(() => { + reset(currentTag); + }, [currentTag]); + + const { register, handleSubmit, watch, reset, control } = useForm({ defaultValues: currentTag }); + + const submitTagUpdate = handleSubmit((data) => updateTag(data)); + + const autoUpdateTag = useCallback(useDebounce(submitTagUpdate, 500), []); + + useEffect(() => { + const subscription = watch(() => autoUpdateTag()); + return () => subscription.unsubscribe(); + }); + return ( - + + { + createTag({ + name: newName, + color: newColor + }); + }} + loading={isLoading} + ctaLabel="Create" + trigger={ + + } + > +
+ + setNewName(e.target.value)} + className="w-full pl-[40px]" + placeholder="Name" + /> +
+
+
+ } + /> + + +
+ {tags?.map((tag) => ( +
setSelectedTag(tag.id === selectedTag ? null : tag.id)} + key={tag.id} + className={clsx( + 'flex items-center rounded px-1.5 py-0.5', + selectedTag == tag.id && 'ring' + )} + style={{ backgroundColor: tag.color + 'CC' }} + > + {tag.name} +
+ ))} +
+
+ {currentTag ? ( +
+
+
+ + Color + +
+ ( + + )} + /> + + +
+
+
+ + Name + + +
+
+ { + deleteTag({ id: currentTag.id }); + }} + loading={tagDeleteLoading} + ctaDanger + ctaLabel="Delete" + trigger={ + + } + /> +
+ + + +
+ ) : ( +
No Tag Selected
+ )} ); } diff --git a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx index 5c12f8c3e..1f05c1d09 100644 --- a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx +++ b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx @@ -65,7 +65,7 @@ export default function LibrarySettings() { } }); - const { data: libraries } = useBridgeQuery('NodeGetLibraries'); + const { data: libraries } = useBridgeQuery('GetLibraries'); function createNewLib() { if (newLibName) { diff --git a/packages/interface/src/style.scss b/packages/interface/src/style.scss index e58d884fb..ef44fb5b1 100644 --- a/packages/interface/src/style.scss +++ b/packages/interface/src/style.scss @@ -130,3 +130,24 @@ body { .dialog-content[data-state='closed'] { animation: bounceDown 100ms ease-in forwards; } + +.picker { + position: relative; +} + +.swatch { + width: 28px; + height: 28px; + border-radius: 8px; + border: 3px solid #fff; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1); + cursor: pointer; +} + +.popover { + position: absolute; + top: calc(100% + 2px); + left: 0; + border-radius: 9px; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb35be3d431291c12309c5aa0cbc2fd996fa1ceb..84e1d945b4cca4c1cf2787dab4b852777543054f 100644 GIT binary patch delta 256 zcmcbxSMA6_wGG8ed^!0=X{9+<3UQ`-rh0~xQNB_m#o^fniDnr-$@)o2k--KYW^N`$o`qR~QQAQjeonE@i~YKRseEyYTeH4o2DO g0%43o(;uiYsZM_|kzKUCqLUqnIks1Haz=;(0E|*rEdT%j delta 76 zcmX?dQ0>BAwGG8elP4&ZO_tE&*=(qMpB2bqpX{eOzga@7T|$cyh?#(x8Hibcm=%cG WmP=@{C$#5vvjZ{5_PlP+Fi`;ggcvgb From 411a628a0524ec88acc0eb6b12647a18eb0578bc Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Tue, 19 Jul 2022 00:57:32 +0800 Subject: [PATCH 21/51] fix Typescript errors --- packages/interface/src/components/jobs/RunningJobsWidget.tsx | 4 ++-- packages/interface/src/screens/Debug.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interface/src/components/jobs/RunningJobsWidget.tsx b/packages/interface/src/components/jobs/RunningJobsWidget.tsx index 0ef77eccf..b1a5e62a9 100644 --- a/packages/interface/src/components/jobs/RunningJobsWidget.tsx +++ b/packages/interface/src/components/jobs/RunningJobsWidget.tsx @@ -1,5 +1,5 @@ import { Transition } from '@headlessui/react'; -import { useBridgeQuery } from '@sd/client'; +import { useBridgeQuery, useLibraryQuery } from '@sd/client'; import clsx from 'clsx'; import React, { DetailedHTMLProps, HTMLAttributes } from 'react'; @@ -51,7 +51,7 @@ const MiddleTruncatedText = ({ }; export default function RunningJobsWidget() { - const { data: jobs } = useBridgeQuery('GetRunningJobs'); + const { data: jobs } = useLibraryQuery('GetRunningJobs'); return (
diff --git a/packages/interface/src/screens/Debug.tsx b/packages/interface/src/screens/Debug.tsx index 254a1b90a..003850d65 100644 --- a/packages/interface/src/screens/Debug.tsx +++ b/packages/interface/src/screens/Debug.tsx @@ -9,7 +9,7 @@ export const DebugScreen: React.FC<{}> = (props) => { const appPropsContext = useContext(AppPropsContext); const { data: nodeState } = useBridgeQuery('GetNode'); const { data: libraryState } = useBridgeQuery('GetLibraries'); - const { data: jobs } = useBridgeQuery('GetRunningJobs'); + const { data: jobs } = useLibraryQuery('GetRunningJobs'); const { data: jobHistory } = useLibraryQuery('GetJobHistory'); // const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', { // onMutate: () => { From 8c9521060db64054ff5b73f883e4e068f39eba4a Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Tue, 19 Jul 2022 09:59:13 +0800 Subject: [PATCH 22/51] Remove frozen flag from clippy workflow --- .github/workflows/clippy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 1a11968c4..5c3cf0fb3 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -47,10 +47,10 @@ jobs: - name: Generate Prisma client working-directory: core if: steps.cache-prisma.outputs.cache-hit != 'true' - run: cargo run --frozen -p prisma-cli --release -- generate + run: cargo run -p prisma-cli --release -- generate - name: Run Clippy uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features \ No newline at end of file + args: --all-features From 3b8cb99c71055ff5614659f982df8fc2181be5db Mon Sep 17 00:00:00 2001 From: xPolar <50601857+xPolar@users.noreply.github.com> Date: Mon, 18 Jul 2022 22:23:46 -0700 Subject: [PATCH 23/51] Don't show Tags on the sidebar if no tags exist. (#342) --- .../interface/src/components/file/Sidebar.tsx | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/interface/src/components/file/Sidebar.tsx b/packages/interface/src/components/file/Sidebar.tsx index ca4052c0e..16d342177 100644 --- a/packages/interface/src/components/file/Sidebar.tsx +++ b/packages/interface/src/components/file/Sidebar.tsx @@ -238,20 +238,24 @@ export const Sidebar: React.FC = (props) => { )}
-
- Tags -
- {tags?.slice(0, 6).map((tag, index) => ( - -
- {tag.name} - - ))} + {tags?.length ? ( +
+ Tags +
+ {tags?.slice(0, 6).map((tag, index) => ( + +
+ {tag.name} + + ))} +
-
+ ) : ( + <> + )}
{/*
From 2629da8323f2cb97df6625e3f57e97b6e91047a4 Mon Sep 17 00:00:00 2001 From: Utku <74243531+utkubakir@users.noreply.github.com> Date: Tue, 26 Jul 2022 06:08:44 +0300 Subject: [PATCH 24/51] Introducing assets package (#347) * Move file Icons & Spacedrive logo to assets folder * Fix all imports Co-authored-by: Utku <> --- packages/assets/README.md | 1 + .../{interface/src => }/assets/icons/ai.svg | 0 .../src => }/assets/icons/angular.svg | 0 .../src => }/assets/icons/audio-mp3.svg | 0 .../src => }/assets/icons/audio-ogg.svg | 0 .../src => }/assets/icons/audio-wav.svg | 0 .../src => }/assets/icons/audio.svg | 0 .../src => }/assets/icons/babel.svg | 0 .../{interface/src => }/assets/icons/bat.svg | 0 .../src => }/assets/icons/bicep.svg | 0 .../src => }/assets/icons/binary.svg | 0 .../src => }/assets/icons/blade.svg | 0 .../src => }/assets/icons/browserslist.svg | 0 .../src => }/assets/icons/bsconfig.svg | 0 .../src => }/assets/icons/bundler.svg | 0 .../{interface/src => }/assets/icons/c.svg | 0 .../{interface/src => }/assets/icons/cert.svg | 0 .../src => }/assets/icons/cheader.svg | 0 .../{interface/src => }/assets/icons/cli.svg | 0 .../src => }/assets/icons/compodoc.svg | 0 .../src => }/assets/icons/composer.svg | 0 .../{interface/src => }/assets/icons/conf.svg | 0 .../{interface/src => }/assets/icons/cpp.svg | 0 .../src => }/assets/icons/csharp.svg | 0 .../src => }/assets/icons/cshtml.svg | 0 .../src => }/assets/icons/css-map.svg | 0 .../{interface/src => }/assets/icons/css.svg | 0 .../{interface/src => }/assets/icons/csv.svg | 0 .../src => }/assets/icons/dartlang.svg | 0 .../src => }/assets/icons/docker-debug.svg | 0 .../src => }/assets/icons/docker-ignore.svg | 0 .../src => }/assets/icons/docker.svg | 0 .../src => }/assets/icons/editorconfig.svg | 0 .../{interface/src => }/assets/icons/eex.svg | 0 .../src => }/assets/icons/elixir.svg | 0 .../{interface/src => }/assets/icons/elm.svg | 0 .../{interface/src => }/assets/icons/env.svg | 0 .../{interface/src => }/assets/icons/erb.svg | 0 .../src => }/assets/icons/erlang.svg | 0 .../src => }/assets/icons/eslint.svg | 0 .../{interface/src => }/assets/icons/exs.svg | 0 .../{interface/src => }/assets/icons/exx.svg | 0 .../{interface/src => }/assets/icons/file.svg | 0 .../src => }/assets/icons/folder-light.svg | 0 .../src => }/assets/icons/folder-open.svg | 0 .../src => }/assets/icons/folder.svg | 0 .../src => }/assets/icons/font-otf.svg | 0 .../src => }/assets/icons/font-ttf.svg | 0 .../src => }/assets/icons/font-woff.svg | 0 .../src => }/assets/icons/font-woff2.svg | 0 .../{interface/src => }/assets/icons/git.svg | 0 .../src => }/assets/icons/go-package.svg | 0 .../{interface/src => }/assets/icons/go.svg | 0 .../src => }/assets/icons/gradle.svg | 0 .../src => }/assets/icons/graphql.svg | 0 .../src => }/assets/icons/groovy.svg | 0 .../src => }/assets/icons/grunt.svg | 0 .../{interface/src => }/assets/icons/gulp.svg | 0 .../{interface/src => }/assets/icons/haml.svg | 0 .../src => }/assets/icons/handlebars.svg | 0 .../src => }/assets/icons/haskell.svg | 0 .../{interface/src => }/assets/icons/html.svg | 0 .../src => }/assets/icons/image-gif.svg | 0 .../src => }/assets/icons/image-ico.svg | 0 .../src => }/assets/icons/image-jpg.svg | 0 .../src => }/assets/icons/image-png.svg | 0 .../src => }/assets/icons/image-webp.svg | 0 .../src => }/assets/icons/image.svg | 0 .../{interface/src => }/assets/icons/info.svg | 0 .../src => }/assets/icons/ipynb.svg | 0 .../{interface/src => }/assets/icons/java.svg | 0 .../src => }/assets/icons/jenkins.svg | 0 .../{interface/src => }/assets/icons/jest.svg | 0 .../src => }/assets/icons/jinja.svg | 0 .../src => }/assets/icons/js-map.svg | 0 .../{interface/src => }/assets/icons/js.svg | 0 .../{interface/src => }/assets/icons/json.svg | 0 .../{interface/src => }/assets/icons/jsp.svg | 0 .../src => }/assets/icons/julia.svg | 0 .../src => }/assets/icons/karma.svg | 0 .../{interface/src => }/assets/icons/key.svg | 0 .../{interface/src => }/assets/icons/less.svg | 0 .../src => }/assets/icons/license.svg | 0 .../assets/icons/lighteditorconfig.svg | 0 .../src => }/assets/icons/liquid.svg | 0 .../{interface/src => }/assets/icons/llvm.svg | 0 .../{interface/src => }/assets/icons/log.svg | 0 .../{interface/src => }/assets/icons/lua.svg | 0 .../{interface/src => }/assets/icons/m.svg | 0 .../src => }/assets/icons/markdown.svg | 0 .../{interface/src => }/assets/icons/mint.svg | 0 .../{interface/src => }/assets/icons/mov.svg | 0 .../{interface/src => }/assets/icons/mp4.svg | 0 .../assets/icons/nestjs-controller.svg | 0 .../assets/icons/nestjs-decorator.svg | 0 .../src => }/assets/icons/nestjs-filter.svg | 0 .../src => }/assets/icons/nestjs-guard.svg | 0 .../src => }/assets/icons/nestjs-module.svg | 0 .../src => }/assets/icons/nestjs-service.svg | 0 .../src => }/assets/icons/nestjs.svg | 0 .../src => }/assets/icons/netlify.svg | 0 .../src => }/assets/icons/nginx.svg | 0 .../{interface/src => }/assets/icons/nim.svg | 0 .../{interface/src => }/assets/icons/njk.svg | 0 .../src => }/assets/icons/nodemon.svg | 0 .../src => }/assets/icons/npm-lock.svg | 0 .../{interface/src => }/assets/icons/npm.svg | 0 .../{interface/src => }/assets/icons/nuxt.svg | 0 .../{interface/src => }/assets/icons/nvm.svg | 0 .../src => }/assets/icons/opengl.svg | 0 .../{interface/src => }/assets/icons/pdf.svg | 0 .../src => }/assets/icons/photoshop.svg | 0 .../{interface/src => }/assets/icons/php.svg | 0 .../src => }/assets/icons/postcss-config.svg | 0 .../src => }/assets/icons/powershell-data.svg | 0 .../assets/icons/powershell-module.svg | 0 .../src => }/assets/icons/powershell.svg | 0 .../src => }/assets/icons/prettier.svg | 0 .../src => }/assets/icons/prisma.svg | 0 .../src => }/assets/icons/prolog.svg | 0 .../{interface/src => }/assets/icons/pug.svg | 0 .../src => }/assets/icons/python.svg | 0 .../{interface/src => }/assets/icons/qt.svg | 0 .../src => }/assets/icons/razor.svg | 0 .../src => }/assets/icons/react-js.svg | 0 .../src => }/assets/icons/react-ts.svg | 0 .../src => }/assets/icons/readme.svg | 0 .../src => }/assets/icons/rescript.svg | 0 .../src => }/assets/icons/rjson.svg | 0 .../src => }/assets/icons/robots.svg | 0 .../src => }/assets/icons/rollup.svg | 0 .../{interface/src => }/assets/icons/ruby.svg | 0 .../{interface/src => }/assets/icons/rust.svg | 0 .../{interface/src => }/assets/icons/sass.svg | 0 .../{interface/src => }/assets/icons/scss.svg | 0 .../src => }/assets/icons/shell.svg | 0 .../src => }/assets/icons/smarty.svg | 0 .../{interface/src => }/assets/icons/sol.svg | 0 .../{interface/src => }/assets/icons/sql.svg | 0 .../src => }/assets/icons/storybook.svg | 0 .../src => }/assets/icons/stylelint.svg | 0 .../src => }/assets/icons/stylus.svg | 0 .../src => }/assets/icons/svelte.svg | 0 .../{interface/src => }/assets/icons/svg.svg | 0 .../src => }/assets/icons/swift.svg | 0 .../src => }/assets/icons/symfony.svg | 0 .../src => }/assets/icons/tailwind.svg | 0 .../src => }/assets/icons/test-js.svg | 0 .../src => }/assets/icons/test-ts.svg | 0 .../{interface/src => }/assets/icons/tmpl.svg | 0 .../{interface/src => }/assets/icons/toml.svg | 0 .../src => }/assets/icons/travis.svg | 0 .../src => }/assets/icons/tsconfig.svg | 0 .../{interface/src => }/assets/icons/tsx.svg | 0 .../{interface/src => }/assets/icons/twig.svg | 0 .../{interface/src => }/assets/icons/txt.svg | 0 .../src => }/assets/icons/typescript-def.svg | 0 .../src => }/assets/icons/typescript.svg | 0 .../{interface/src => }/assets/icons/ui.svg | 0 .../{interface/src => }/assets/icons/user.svg | 0 .../src => }/assets/icons/vercel.svg | 0 .../src => }/assets/icons/video.svg | 0 .../{interface/src => }/assets/icons/vite.svg | 0 .../src => }/assets/icons/vscode.svg | 0 .../{interface/src => }/assets/icons/vue.svg | 0 .../{interface/src => }/assets/icons/wasm.svg | 0 .../src => }/assets/icons/webpack.svg | 0 .../src => }/assets/icons/windi.svg | 0 .../{interface/src => }/assets/icons/xml.svg | 0 .../{interface/src => }/assets/icons/yaml.svg | 0 .../src => }/assets/icons/yarn-error.svg | 0 .../{interface/src => }/assets/icons/yarn.svg | 0 .../{interface/src => }/assets/icons/zip.svg | 0 .../assets/images/spacedrive_logo.png | Bin packages/assets/package.json | 6 + packages/interface/package.json | 1 + .../interface/scripts/generateSvgImports.mjs | 5 +- packages/interface/src/assets/icons/index.ts | 344 +++++++++--------- pnpm-lock.yaml | Bin 630980 -> 631267 bytes 179 files changed, 183 insertions(+), 174 deletions(-) create mode 100644 packages/assets/README.md rename packages/{interface/src => }/assets/icons/ai.svg (100%) rename packages/{interface/src => }/assets/icons/angular.svg (100%) rename packages/{interface/src => }/assets/icons/audio-mp3.svg (100%) rename packages/{interface/src => }/assets/icons/audio-ogg.svg (100%) rename packages/{interface/src => }/assets/icons/audio-wav.svg (100%) rename packages/{interface/src => }/assets/icons/audio.svg (100%) rename packages/{interface/src => }/assets/icons/babel.svg (100%) rename packages/{interface/src => }/assets/icons/bat.svg (100%) rename packages/{interface/src => }/assets/icons/bicep.svg (100%) rename packages/{interface/src => }/assets/icons/binary.svg (100%) rename packages/{interface/src => }/assets/icons/blade.svg (100%) rename packages/{interface/src => }/assets/icons/browserslist.svg (100%) rename packages/{interface/src => }/assets/icons/bsconfig.svg (100%) rename packages/{interface/src => }/assets/icons/bundler.svg (100%) rename packages/{interface/src => }/assets/icons/c.svg (100%) rename packages/{interface/src => }/assets/icons/cert.svg (100%) rename packages/{interface/src => }/assets/icons/cheader.svg (100%) rename packages/{interface/src => }/assets/icons/cli.svg (100%) rename packages/{interface/src => }/assets/icons/compodoc.svg (100%) rename packages/{interface/src => }/assets/icons/composer.svg (100%) rename packages/{interface/src => }/assets/icons/conf.svg (100%) rename packages/{interface/src => }/assets/icons/cpp.svg (100%) rename packages/{interface/src => }/assets/icons/csharp.svg (100%) rename packages/{interface/src => }/assets/icons/cshtml.svg (100%) rename packages/{interface/src => }/assets/icons/css-map.svg (100%) rename packages/{interface/src => }/assets/icons/css.svg (100%) rename packages/{interface/src => }/assets/icons/csv.svg (100%) rename packages/{interface/src => }/assets/icons/dartlang.svg (100%) rename packages/{interface/src => }/assets/icons/docker-debug.svg (100%) rename packages/{interface/src => }/assets/icons/docker-ignore.svg (100%) rename packages/{interface/src => }/assets/icons/docker.svg (100%) rename packages/{interface/src => }/assets/icons/editorconfig.svg (100%) rename packages/{interface/src => }/assets/icons/eex.svg (100%) rename packages/{interface/src => }/assets/icons/elixir.svg (100%) rename packages/{interface/src => }/assets/icons/elm.svg (100%) rename packages/{interface/src => }/assets/icons/env.svg (100%) rename packages/{interface/src => }/assets/icons/erb.svg (100%) rename packages/{interface/src => }/assets/icons/erlang.svg (100%) rename packages/{interface/src => }/assets/icons/eslint.svg (100%) rename packages/{interface/src => }/assets/icons/exs.svg (100%) rename packages/{interface/src => }/assets/icons/exx.svg (100%) rename packages/{interface/src => }/assets/icons/file.svg (100%) rename packages/{interface/src => }/assets/icons/folder-light.svg (100%) rename packages/{interface/src => }/assets/icons/folder-open.svg (100%) rename packages/{interface/src => }/assets/icons/folder.svg (100%) rename packages/{interface/src => }/assets/icons/font-otf.svg (100%) rename packages/{interface/src => }/assets/icons/font-ttf.svg (100%) rename packages/{interface/src => }/assets/icons/font-woff.svg (100%) rename packages/{interface/src => }/assets/icons/font-woff2.svg (100%) rename packages/{interface/src => }/assets/icons/git.svg (100%) rename packages/{interface/src => }/assets/icons/go-package.svg (100%) rename packages/{interface/src => }/assets/icons/go.svg (100%) rename packages/{interface/src => }/assets/icons/gradle.svg (100%) rename packages/{interface/src => }/assets/icons/graphql.svg (100%) rename packages/{interface/src => }/assets/icons/groovy.svg (100%) rename packages/{interface/src => }/assets/icons/grunt.svg (100%) rename packages/{interface/src => }/assets/icons/gulp.svg (100%) rename packages/{interface/src => }/assets/icons/haml.svg (100%) rename packages/{interface/src => }/assets/icons/handlebars.svg (100%) rename packages/{interface/src => }/assets/icons/haskell.svg (100%) rename packages/{interface/src => }/assets/icons/html.svg (100%) rename packages/{interface/src => }/assets/icons/image-gif.svg (100%) rename packages/{interface/src => }/assets/icons/image-ico.svg (100%) rename packages/{interface/src => }/assets/icons/image-jpg.svg (100%) rename packages/{interface/src => }/assets/icons/image-png.svg (100%) rename packages/{interface/src => }/assets/icons/image-webp.svg (100%) rename packages/{interface/src => }/assets/icons/image.svg (100%) rename packages/{interface/src => }/assets/icons/info.svg (100%) rename packages/{interface/src => }/assets/icons/ipynb.svg (100%) rename packages/{interface/src => }/assets/icons/java.svg (100%) rename packages/{interface/src => }/assets/icons/jenkins.svg (100%) rename packages/{interface/src => }/assets/icons/jest.svg (100%) rename packages/{interface/src => }/assets/icons/jinja.svg (100%) rename packages/{interface/src => }/assets/icons/js-map.svg (100%) rename packages/{interface/src => }/assets/icons/js.svg (100%) rename packages/{interface/src => }/assets/icons/json.svg (100%) rename packages/{interface/src => }/assets/icons/jsp.svg (100%) rename packages/{interface/src => }/assets/icons/julia.svg (100%) rename packages/{interface/src => }/assets/icons/karma.svg (100%) rename packages/{interface/src => }/assets/icons/key.svg (100%) rename packages/{interface/src => }/assets/icons/less.svg (100%) rename packages/{interface/src => }/assets/icons/license.svg (100%) rename packages/{interface/src => }/assets/icons/lighteditorconfig.svg (100%) rename packages/{interface/src => }/assets/icons/liquid.svg (100%) rename packages/{interface/src => }/assets/icons/llvm.svg (100%) rename packages/{interface/src => }/assets/icons/log.svg (100%) rename packages/{interface/src => }/assets/icons/lua.svg (100%) rename packages/{interface/src => }/assets/icons/m.svg (100%) rename packages/{interface/src => }/assets/icons/markdown.svg (100%) rename packages/{interface/src => }/assets/icons/mint.svg (100%) rename packages/{interface/src => }/assets/icons/mov.svg (100%) rename packages/{interface/src => }/assets/icons/mp4.svg (100%) rename packages/{interface/src => }/assets/icons/nestjs-controller.svg (100%) rename packages/{interface/src => }/assets/icons/nestjs-decorator.svg (100%) rename packages/{interface/src => }/assets/icons/nestjs-filter.svg (100%) rename packages/{interface/src => }/assets/icons/nestjs-guard.svg (100%) rename packages/{interface/src => }/assets/icons/nestjs-module.svg (100%) rename packages/{interface/src => }/assets/icons/nestjs-service.svg (100%) rename packages/{interface/src => }/assets/icons/nestjs.svg (100%) rename packages/{interface/src => }/assets/icons/netlify.svg (100%) rename packages/{interface/src => }/assets/icons/nginx.svg (100%) rename packages/{interface/src => }/assets/icons/nim.svg (100%) rename packages/{interface/src => }/assets/icons/njk.svg (100%) rename packages/{interface/src => }/assets/icons/nodemon.svg (100%) rename packages/{interface/src => }/assets/icons/npm-lock.svg (100%) rename packages/{interface/src => }/assets/icons/npm.svg (100%) rename packages/{interface/src => }/assets/icons/nuxt.svg (100%) rename packages/{interface/src => }/assets/icons/nvm.svg (100%) rename packages/{interface/src => }/assets/icons/opengl.svg (100%) rename packages/{interface/src => }/assets/icons/pdf.svg (100%) rename packages/{interface/src => }/assets/icons/photoshop.svg (100%) rename packages/{interface/src => }/assets/icons/php.svg (100%) rename packages/{interface/src => }/assets/icons/postcss-config.svg (100%) rename packages/{interface/src => }/assets/icons/powershell-data.svg (100%) rename packages/{interface/src => }/assets/icons/powershell-module.svg (100%) rename packages/{interface/src => }/assets/icons/powershell.svg (100%) rename packages/{interface/src => }/assets/icons/prettier.svg (100%) rename packages/{interface/src => }/assets/icons/prisma.svg (100%) rename packages/{interface/src => }/assets/icons/prolog.svg (100%) rename packages/{interface/src => }/assets/icons/pug.svg (100%) rename packages/{interface/src => }/assets/icons/python.svg (100%) rename packages/{interface/src => }/assets/icons/qt.svg (100%) rename packages/{interface/src => }/assets/icons/razor.svg (100%) rename packages/{interface/src => }/assets/icons/react-js.svg (100%) rename packages/{interface/src => }/assets/icons/react-ts.svg (100%) rename packages/{interface/src => }/assets/icons/readme.svg (100%) rename packages/{interface/src => }/assets/icons/rescript.svg (100%) rename packages/{interface/src => }/assets/icons/rjson.svg (100%) rename packages/{interface/src => }/assets/icons/robots.svg (100%) rename packages/{interface/src => }/assets/icons/rollup.svg (100%) rename packages/{interface/src => }/assets/icons/ruby.svg (100%) rename packages/{interface/src => }/assets/icons/rust.svg (100%) rename packages/{interface/src => }/assets/icons/sass.svg (100%) rename packages/{interface/src => }/assets/icons/scss.svg (100%) rename packages/{interface/src => }/assets/icons/shell.svg (100%) rename packages/{interface/src => }/assets/icons/smarty.svg (100%) rename packages/{interface/src => }/assets/icons/sol.svg (100%) rename packages/{interface/src => }/assets/icons/sql.svg (100%) rename packages/{interface/src => }/assets/icons/storybook.svg (100%) rename packages/{interface/src => }/assets/icons/stylelint.svg (100%) rename packages/{interface/src => }/assets/icons/stylus.svg (100%) rename packages/{interface/src => }/assets/icons/svelte.svg (100%) rename packages/{interface/src => }/assets/icons/svg.svg (100%) rename packages/{interface/src => }/assets/icons/swift.svg (100%) rename packages/{interface/src => }/assets/icons/symfony.svg (100%) rename packages/{interface/src => }/assets/icons/tailwind.svg (100%) rename packages/{interface/src => }/assets/icons/test-js.svg (100%) rename packages/{interface/src => }/assets/icons/test-ts.svg (100%) rename packages/{interface/src => }/assets/icons/tmpl.svg (100%) rename packages/{interface/src => }/assets/icons/toml.svg (100%) rename packages/{interface/src => }/assets/icons/travis.svg (100%) rename packages/{interface/src => }/assets/icons/tsconfig.svg (100%) rename packages/{interface/src => }/assets/icons/tsx.svg (100%) rename packages/{interface/src => }/assets/icons/twig.svg (100%) rename packages/{interface/src => }/assets/icons/txt.svg (100%) rename packages/{interface/src => }/assets/icons/typescript-def.svg (100%) rename packages/{interface/src => }/assets/icons/typescript.svg (100%) rename packages/{interface/src => }/assets/icons/ui.svg (100%) rename packages/{interface/src => }/assets/icons/user.svg (100%) rename packages/{interface/src => }/assets/icons/vercel.svg (100%) rename packages/{interface/src => }/assets/icons/video.svg (100%) rename packages/{interface/src => }/assets/icons/vite.svg (100%) rename packages/{interface/src => }/assets/icons/vscode.svg (100%) rename packages/{interface/src => }/assets/icons/vue.svg (100%) rename packages/{interface/src => }/assets/icons/wasm.svg (100%) rename packages/{interface/src => }/assets/icons/webpack.svg (100%) rename packages/{interface/src => }/assets/icons/windi.svg (100%) rename packages/{interface/src => }/assets/icons/xml.svg (100%) rename packages/{interface/src => }/assets/icons/yaml.svg (100%) rename packages/{interface/src => }/assets/icons/yarn-error.svg (100%) rename packages/{interface/src => }/assets/icons/yarn.svg (100%) rename packages/{interface/src => }/assets/icons/zip.svg (100%) rename packages/{interface/src => }/assets/images/spacedrive_logo.png (100%) create mode 100644 packages/assets/package.json diff --git a/packages/assets/README.md b/packages/assets/README.md new file mode 100644 index 000000000..61a5927fe --- /dev/null +++ b/packages/assets/README.md @@ -0,0 +1 @@ +Shared assets between all apps, like SVG's are put here. diff --git a/packages/interface/src/assets/icons/ai.svg b/packages/assets/icons/ai.svg similarity index 100% rename from packages/interface/src/assets/icons/ai.svg rename to packages/assets/icons/ai.svg diff --git a/packages/interface/src/assets/icons/angular.svg b/packages/assets/icons/angular.svg similarity index 100% rename from packages/interface/src/assets/icons/angular.svg rename to packages/assets/icons/angular.svg diff --git a/packages/interface/src/assets/icons/audio-mp3.svg b/packages/assets/icons/audio-mp3.svg similarity index 100% rename from packages/interface/src/assets/icons/audio-mp3.svg rename to packages/assets/icons/audio-mp3.svg diff --git a/packages/interface/src/assets/icons/audio-ogg.svg b/packages/assets/icons/audio-ogg.svg similarity index 100% rename from packages/interface/src/assets/icons/audio-ogg.svg rename to packages/assets/icons/audio-ogg.svg diff --git a/packages/interface/src/assets/icons/audio-wav.svg b/packages/assets/icons/audio-wav.svg similarity index 100% rename from packages/interface/src/assets/icons/audio-wav.svg rename to packages/assets/icons/audio-wav.svg diff --git a/packages/interface/src/assets/icons/audio.svg b/packages/assets/icons/audio.svg similarity index 100% rename from packages/interface/src/assets/icons/audio.svg rename to packages/assets/icons/audio.svg diff --git a/packages/interface/src/assets/icons/babel.svg b/packages/assets/icons/babel.svg similarity index 100% rename from packages/interface/src/assets/icons/babel.svg rename to packages/assets/icons/babel.svg diff --git a/packages/interface/src/assets/icons/bat.svg b/packages/assets/icons/bat.svg similarity index 100% rename from packages/interface/src/assets/icons/bat.svg rename to packages/assets/icons/bat.svg diff --git a/packages/interface/src/assets/icons/bicep.svg b/packages/assets/icons/bicep.svg similarity index 100% rename from packages/interface/src/assets/icons/bicep.svg rename to packages/assets/icons/bicep.svg diff --git a/packages/interface/src/assets/icons/binary.svg b/packages/assets/icons/binary.svg similarity index 100% rename from packages/interface/src/assets/icons/binary.svg rename to packages/assets/icons/binary.svg diff --git a/packages/interface/src/assets/icons/blade.svg b/packages/assets/icons/blade.svg similarity index 100% rename from packages/interface/src/assets/icons/blade.svg rename to packages/assets/icons/blade.svg diff --git a/packages/interface/src/assets/icons/browserslist.svg b/packages/assets/icons/browserslist.svg similarity index 100% rename from packages/interface/src/assets/icons/browserslist.svg rename to packages/assets/icons/browserslist.svg diff --git a/packages/interface/src/assets/icons/bsconfig.svg b/packages/assets/icons/bsconfig.svg similarity index 100% rename from packages/interface/src/assets/icons/bsconfig.svg rename to packages/assets/icons/bsconfig.svg diff --git a/packages/interface/src/assets/icons/bundler.svg b/packages/assets/icons/bundler.svg similarity index 100% rename from packages/interface/src/assets/icons/bundler.svg rename to packages/assets/icons/bundler.svg diff --git a/packages/interface/src/assets/icons/c.svg b/packages/assets/icons/c.svg similarity index 100% rename from packages/interface/src/assets/icons/c.svg rename to packages/assets/icons/c.svg diff --git a/packages/interface/src/assets/icons/cert.svg b/packages/assets/icons/cert.svg similarity index 100% rename from packages/interface/src/assets/icons/cert.svg rename to packages/assets/icons/cert.svg diff --git a/packages/interface/src/assets/icons/cheader.svg b/packages/assets/icons/cheader.svg similarity index 100% rename from packages/interface/src/assets/icons/cheader.svg rename to packages/assets/icons/cheader.svg diff --git a/packages/interface/src/assets/icons/cli.svg b/packages/assets/icons/cli.svg similarity index 100% rename from packages/interface/src/assets/icons/cli.svg rename to packages/assets/icons/cli.svg diff --git a/packages/interface/src/assets/icons/compodoc.svg b/packages/assets/icons/compodoc.svg similarity index 100% rename from packages/interface/src/assets/icons/compodoc.svg rename to packages/assets/icons/compodoc.svg diff --git a/packages/interface/src/assets/icons/composer.svg b/packages/assets/icons/composer.svg similarity index 100% rename from packages/interface/src/assets/icons/composer.svg rename to packages/assets/icons/composer.svg diff --git a/packages/interface/src/assets/icons/conf.svg b/packages/assets/icons/conf.svg similarity index 100% rename from packages/interface/src/assets/icons/conf.svg rename to packages/assets/icons/conf.svg diff --git a/packages/interface/src/assets/icons/cpp.svg b/packages/assets/icons/cpp.svg similarity index 100% rename from packages/interface/src/assets/icons/cpp.svg rename to packages/assets/icons/cpp.svg diff --git a/packages/interface/src/assets/icons/csharp.svg b/packages/assets/icons/csharp.svg similarity index 100% rename from packages/interface/src/assets/icons/csharp.svg rename to packages/assets/icons/csharp.svg diff --git a/packages/interface/src/assets/icons/cshtml.svg b/packages/assets/icons/cshtml.svg similarity index 100% rename from packages/interface/src/assets/icons/cshtml.svg rename to packages/assets/icons/cshtml.svg diff --git a/packages/interface/src/assets/icons/css-map.svg b/packages/assets/icons/css-map.svg similarity index 100% rename from packages/interface/src/assets/icons/css-map.svg rename to packages/assets/icons/css-map.svg diff --git a/packages/interface/src/assets/icons/css.svg b/packages/assets/icons/css.svg similarity index 100% rename from packages/interface/src/assets/icons/css.svg rename to packages/assets/icons/css.svg diff --git a/packages/interface/src/assets/icons/csv.svg b/packages/assets/icons/csv.svg similarity index 100% rename from packages/interface/src/assets/icons/csv.svg rename to packages/assets/icons/csv.svg diff --git a/packages/interface/src/assets/icons/dartlang.svg b/packages/assets/icons/dartlang.svg similarity index 100% rename from packages/interface/src/assets/icons/dartlang.svg rename to packages/assets/icons/dartlang.svg diff --git a/packages/interface/src/assets/icons/docker-debug.svg b/packages/assets/icons/docker-debug.svg similarity index 100% rename from packages/interface/src/assets/icons/docker-debug.svg rename to packages/assets/icons/docker-debug.svg diff --git a/packages/interface/src/assets/icons/docker-ignore.svg b/packages/assets/icons/docker-ignore.svg similarity index 100% rename from packages/interface/src/assets/icons/docker-ignore.svg rename to packages/assets/icons/docker-ignore.svg diff --git a/packages/interface/src/assets/icons/docker.svg b/packages/assets/icons/docker.svg similarity index 100% rename from packages/interface/src/assets/icons/docker.svg rename to packages/assets/icons/docker.svg diff --git a/packages/interface/src/assets/icons/editorconfig.svg b/packages/assets/icons/editorconfig.svg similarity index 100% rename from packages/interface/src/assets/icons/editorconfig.svg rename to packages/assets/icons/editorconfig.svg diff --git a/packages/interface/src/assets/icons/eex.svg b/packages/assets/icons/eex.svg similarity index 100% rename from packages/interface/src/assets/icons/eex.svg rename to packages/assets/icons/eex.svg diff --git a/packages/interface/src/assets/icons/elixir.svg b/packages/assets/icons/elixir.svg similarity index 100% rename from packages/interface/src/assets/icons/elixir.svg rename to packages/assets/icons/elixir.svg diff --git a/packages/interface/src/assets/icons/elm.svg b/packages/assets/icons/elm.svg similarity index 100% rename from packages/interface/src/assets/icons/elm.svg rename to packages/assets/icons/elm.svg diff --git a/packages/interface/src/assets/icons/env.svg b/packages/assets/icons/env.svg similarity index 100% rename from packages/interface/src/assets/icons/env.svg rename to packages/assets/icons/env.svg diff --git a/packages/interface/src/assets/icons/erb.svg b/packages/assets/icons/erb.svg similarity index 100% rename from packages/interface/src/assets/icons/erb.svg rename to packages/assets/icons/erb.svg diff --git a/packages/interface/src/assets/icons/erlang.svg b/packages/assets/icons/erlang.svg similarity index 100% rename from packages/interface/src/assets/icons/erlang.svg rename to packages/assets/icons/erlang.svg diff --git a/packages/interface/src/assets/icons/eslint.svg b/packages/assets/icons/eslint.svg similarity index 100% rename from packages/interface/src/assets/icons/eslint.svg rename to packages/assets/icons/eslint.svg diff --git a/packages/interface/src/assets/icons/exs.svg b/packages/assets/icons/exs.svg similarity index 100% rename from packages/interface/src/assets/icons/exs.svg rename to packages/assets/icons/exs.svg diff --git a/packages/interface/src/assets/icons/exx.svg b/packages/assets/icons/exx.svg similarity index 100% rename from packages/interface/src/assets/icons/exx.svg rename to packages/assets/icons/exx.svg diff --git a/packages/interface/src/assets/icons/file.svg b/packages/assets/icons/file.svg similarity index 100% rename from packages/interface/src/assets/icons/file.svg rename to packages/assets/icons/file.svg diff --git a/packages/interface/src/assets/icons/folder-light.svg b/packages/assets/icons/folder-light.svg similarity index 100% rename from packages/interface/src/assets/icons/folder-light.svg rename to packages/assets/icons/folder-light.svg diff --git a/packages/interface/src/assets/icons/folder-open.svg b/packages/assets/icons/folder-open.svg similarity index 100% rename from packages/interface/src/assets/icons/folder-open.svg rename to packages/assets/icons/folder-open.svg diff --git a/packages/interface/src/assets/icons/folder.svg b/packages/assets/icons/folder.svg similarity index 100% rename from packages/interface/src/assets/icons/folder.svg rename to packages/assets/icons/folder.svg diff --git a/packages/interface/src/assets/icons/font-otf.svg b/packages/assets/icons/font-otf.svg similarity index 100% rename from packages/interface/src/assets/icons/font-otf.svg rename to packages/assets/icons/font-otf.svg diff --git a/packages/interface/src/assets/icons/font-ttf.svg b/packages/assets/icons/font-ttf.svg similarity index 100% rename from packages/interface/src/assets/icons/font-ttf.svg rename to packages/assets/icons/font-ttf.svg diff --git a/packages/interface/src/assets/icons/font-woff.svg b/packages/assets/icons/font-woff.svg similarity index 100% rename from packages/interface/src/assets/icons/font-woff.svg rename to packages/assets/icons/font-woff.svg diff --git a/packages/interface/src/assets/icons/font-woff2.svg b/packages/assets/icons/font-woff2.svg similarity index 100% rename from packages/interface/src/assets/icons/font-woff2.svg rename to packages/assets/icons/font-woff2.svg diff --git a/packages/interface/src/assets/icons/git.svg b/packages/assets/icons/git.svg similarity index 100% rename from packages/interface/src/assets/icons/git.svg rename to packages/assets/icons/git.svg diff --git a/packages/interface/src/assets/icons/go-package.svg b/packages/assets/icons/go-package.svg similarity index 100% rename from packages/interface/src/assets/icons/go-package.svg rename to packages/assets/icons/go-package.svg diff --git a/packages/interface/src/assets/icons/go.svg b/packages/assets/icons/go.svg similarity index 100% rename from packages/interface/src/assets/icons/go.svg rename to packages/assets/icons/go.svg diff --git a/packages/interface/src/assets/icons/gradle.svg b/packages/assets/icons/gradle.svg similarity index 100% rename from packages/interface/src/assets/icons/gradle.svg rename to packages/assets/icons/gradle.svg diff --git a/packages/interface/src/assets/icons/graphql.svg b/packages/assets/icons/graphql.svg similarity index 100% rename from packages/interface/src/assets/icons/graphql.svg rename to packages/assets/icons/graphql.svg diff --git a/packages/interface/src/assets/icons/groovy.svg b/packages/assets/icons/groovy.svg similarity index 100% rename from packages/interface/src/assets/icons/groovy.svg rename to packages/assets/icons/groovy.svg diff --git a/packages/interface/src/assets/icons/grunt.svg b/packages/assets/icons/grunt.svg similarity index 100% rename from packages/interface/src/assets/icons/grunt.svg rename to packages/assets/icons/grunt.svg diff --git a/packages/interface/src/assets/icons/gulp.svg b/packages/assets/icons/gulp.svg similarity index 100% rename from packages/interface/src/assets/icons/gulp.svg rename to packages/assets/icons/gulp.svg diff --git a/packages/interface/src/assets/icons/haml.svg b/packages/assets/icons/haml.svg similarity index 100% rename from packages/interface/src/assets/icons/haml.svg rename to packages/assets/icons/haml.svg diff --git a/packages/interface/src/assets/icons/handlebars.svg b/packages/assets/icons/handlebars.svg similarity index 100% rename from packages/interface/src/assets/icons/handlebars.svg rename to packages/assets/icons/handlebars.svg diff --git a/packages/interface/src/assets/icons/haskell.svg b/packages/assets/icons/haskell.svg similarity index 100% rename from packages/interface/src/assets/icons/haskell.svg rename to packages/assets/icons/haskell.svg diff --git a/packages/interface/src/assets/icons/html.svg b/packages/assets/icons/html.svg similarity index 100% rename from packages/interface/src/assets/icons/html.svg rename to packages/assets/icons/html.svg diff --git a/packages/interface/src/assets/icons/image-gif.svg b/packages/assets/icons/image-gif.svg similarity index 100% rename from packages/interface/src/assets/icons/image-gif.svg rename to packages/assets/icons/image-gif.svg diff --git a/packages/interface/src/assets/icons/image-ico.svg b/packages/assets/icons/image-ico.svg similarity index 100% rename from packages/interface/src/assets/icons/image-ico.svg rename to packages/assets/icons/image-ico.svg diff --git a/packages/interface/src/assets/icons/image-jpg.svg b/packages/assets/icons/image-jpg.svg similarity index 100% rename from packages/interface/src/assets/icons/image-jpg.svg rename to packages/assets/icons/image-jpg.svg diff --git a/packages/interface/src/assets/icons/image-png.svg b/packages/assets/icons/image-png.svg similarity index 100% rename from packages/interface/src/assets/icons/image-png.svg rename to packages/assets/icons/image-png.svg diff --git a/packages/interface/src/assets/icons/image-webp.svg b/packages/assets/icons/image-webp.svg similarity index 100% rename from packages/interface/src/assets/icons/image-webp.svg rename to packages/assets/icons/image-webp.svg diff --git a/packages/interface/src/assets/icons/image.svg b/packages/assets/icons/image.svg similarity index 100% rename from packages/interface/src/assets/icons/image.svg rename to packages/assets/icons/image.svg diff --git a/packages/interface/src/assets/icons/info.svg b/packages/assets/icons/info.svg similarity index 100% rename from packages/interface/src/assets/icons/info.svg rename to packages/assets/icons/info.svg diff --git a/packages/interface/src/assets/icons/ipynb.svg b/packages/assets/icons/ipynb.svg similarity index 100% rename from packages/interface/src/assets/icons/ipynb.svg rename to packages/assets/icons/ipynb.svg diff --git a/packages/interface/src/assets/icons/java.svg b/packages/assets/icons/java.svg similarity index 100% rename from packages/interface/src/assets/icons/java.svg rename to packages/assets/icons/java.svg diff --git a/packages/interface/src/assets/icons/jenkins.svg b/packages/assets/icons/jenkins.svg similarity index 100% rename from packages/interface/src/assets/icons/jenkins.svg rename to packages/assets/icons/jenkins.svg diff --git a/packages/interface/src/assets/icons/jest.svg b/packages/assets/icons/jest.svg similarity index 100% rename from packages/interface/src/assets/icons/jest.svg rename to packages/assets/icons/jest.svg diff --git a/packages/interface/src/assets/icons/jinja.svg b/packages/assets/icons/jinja.svg similarity index 100% rename from packages/interface/src/assets/icons/jinja.svg rename to packages/assets/icons/jinja.svg diff --git a/packages/interface/src/assets/icons/js-map.svg b/packages/assets/icons/js-map.svg similarity index 100% rename from packages/interface/src/assets/icons/js-map.svg rename to packages/assets/icons/js-map.svg diff --git a/packages/interface/src/assets/icons/js.svg b/packages/assets/icons/js.svg similarity index 100% rename from packages/interface/src/assets/icons/js.svg rename to packages/assets/icons/js.svg diff --git a/packages/interface/src/assets/icons/json.svg b/packages/assets/icons/json.svg similarity index 100% rename from packages/interface/src/assets/icons/json.svg rename to packages/assets/icons/json.svg diff --git a/packages/interface/src/assets/icons/jsp.svg b/packages/assets/icons/jsp.svg similarity index 100% rename from packages/interface/src/assets/icons/jsp.svg rename to packages/assets/icons/jsp.svg diff --git a/packages/interface/src/assets/icons/julia.svg b/packages/assets/icons/julia.svg similarity index 100% rename from packages/interface/src/assets/icons/julia.svg rename to packages/assets/icons/julia.svg diff --git a/packages/interface/src/assets/icons/karma.svg b/packages/assets/icons/karma.svg similarity index 100% rename from packages/interface/src/assets/icons/karma.svg rename to packages/assets/icons/karma.svg diff --git a/packages/interface/src/assets/icons/key.svg b/packages/assets/icons/key.svg similarity index 100% rename from packages/interface/src/assets/icons/key.svg rename to packages/assets/icons/key.svg diff --git a/packages/interface/src/assets/icons/less.svg b/packages/assets/icons/less.svg similarity index 100% rename from packages/interface/src/assets/icons/less.svg rename to packages/assets/icons/less.svg diff --git a/packages/interface/src/assets/icons/license.svg b/packages/assets/icons/license.svg similarity index 100% rename from packages/interface/src/assets/icons/license.svg rename to packages/assets/icons/license.svg diff --git a/packages/interface/src/assets/icons/lighteditorconfig.svg b/packages/assets/icons/lighteditorconfig.svg similarity index 100% rename from packages/interface/src/assets/icons/lighteditorconfig.svg rename to packages/assets/icons/lighteditorconfig.svg diff --git a/packages/interface/src/assets/icons/liquid.svg b/packages/assets/icons/liquid.svg similarity index 100% rename from packages/interface/src/assets/icons/liquid.svg rename to packages/assets/icons/liquid.svg diff --git a/packages/interface/src/assets/icons/llvm.svg b/packages/assets/icons/llvm.svg similarity index 100% rename from packages/interface/src/assets/icons/llvm.svg rename to packages/assets/icons/llvm.svg diff --git a/packages/interface/src/assets/icons/log.svg b/packages/assets/icons/log.svg similarity index 100% rename from packages/interface/src/assets/icons/log.svg rename to packages/assets/icons/log.svg diff --git a/packages/interface/src/assets/icons/lua.svg b/packages/assets/icons/lua.svg similarity index 100% rename from packages/interface/src/assets/icons/lua.svg rename to packages/assets/icons/lua.svg diff --git a/packages/interface/src/assets/icons/m.svg b/packages/assets/icons/m.svg similarity index 100% rename from packages/interface/src/assets/icons/m.svg rename to packages/assets/icons/m.svg diff --git a/packages/interface/src/assets/icons/markdown.svg b/packages/assets/icons/markdown.svg similarity index 100% rename from packages/interface/src/assets/icons/markdown.svg rename to packages/assets/icons/markdown.svg diff --git a/packages/interface/src/assets/icons/mint.svg b/packages/assets/icons/mint.svg similarity index 100% rename from packages/interface/src/assets/icons/mint.svg rename to packages/assets/icons/mint.svg diff --git a/packages/interface/src/assets/icons/mov.svg b/packages/assets/icons/mov.svg similarity index 100% rename from packages/interface/src/assets/icons/mov.svg rename to packages/assets/icons/mov.svg diff --git a/packages/interface/src/assets/icons/mp4.svg b/packages/assets/icons/mp4.svg similarity index 100% rename from packages/interface/src/assets/icons/mp4.svg rename to packages/assets/icons/mp4.svg diff --git a/packages/interface/src/assets/icons/nestjs-controller.svg b/packages/assets/icons/nestjs-controller.svg similarity index 100% rename from packages/interface/src/assets/icons/nestjs-controller.svg rename to packages/assets/icons/nestjs-controller.svg diff --git a/packages/interface/src/assets/icons/nestjs-decorator.svg b/packages/assets/icons/nestjs-decorator.svg similarity index 100% rename from packages/interface/src/assets/icons/nestjs-decorator.svg rename to packages/assets/icons/nestjs-decorator.svg diff --git a/packages/interface/src/assets/icons/nestjs-filter.svg b/packages/assets/icons/nestjs-filter.svg similarity index 100% rename from packages/interface/src/assets/icons/nestjs-filter.svg rename to packages/assets/icons/nestjs-filter.svg diff --git a/packages/interface/src/assets/icons/nestjs-guard.svg b/packages/assets/icons/nestjs-guard.svg similarity index 100% rename from packages/interface/src/assets/icons/nestjs-guard.svg rename to packages/assets/icons/nestjs-guard.svg diff --git a/packages/interface/src/assets/icons/nestjs-module.svg b/packages/assets/icons/nestjs-module.svg similarity index 100% rename from packages/interface/src/assets/icons/nestjs-module.svg rename to packages/assets/icons/nestjs-module.svg diff --git a/packages/interface/src/assets/icons/nestjs-service.svg b/packages/assets/icons/nestjs-service.svg similarity index 100% rename from packages/interface/src/assets/icons/nestjs-service.svg rename to packages/assets/icons/nestjs-service.svg diff --git a/packages/interface/src/assets/icons/nestjs.svg b/packages/assets/icons/nestjs.svg similarity index 100% rename from packages/interface/src/assets/icons/nestjs.svg rename to packages/assets/icons/nestjs.svg diff --git a/packages/interface/src/assets/icons/netlify.svg b/packages/assets/icons/netlify.svg similarity index 100% rename from packages/interface/src/assets/icons/netlify.svg rename to packages/assets/icons/netlify.svg diff --git a/packages/interface/src/assets/icons/nginx.svg b/packages/assets/icons/nginx.svg similarity index 100% rename from packages/interface/src/assets/icons/nginx.svg rename to packages/assets/icons/nginx.svg diff --git a/packages/interface/src/assets/icons/nim.svg b/packages/assets/icons/nim.svg similarity index 100% rename from packages/interface/src/assets/icons/nim.svg rename to packages/assets/icons/nim.svg diff --git a/packages/interface/src/assets/icons/njk.svg b/packages/assets/icons/njk.svg similarity index 100% rename from packages/interface/src/assets/icons/njk.svg rename to packages/assets/icons/njk.svg diff --git a/packages/interface/src/assets/icons/nodemon.svg b/packages/assets/icons/nodemon.svg similarity index 100% rename from packages/interface/src/assets/icons/nodemon.svg rename to packages/assets/icons/nodemon.svg diff --git a/packages/interface/src/assets/icons/npm-lock.svg b/packages/assets/icons/npm-lock.svg similarity index 100% rename from packages/interface/src/assets/icons/npm-lock.svg rename to packages/assets/icons/npm-lock.svg diff --git a/packages/interface/src/assets/icons/npm.svg b/packages/assets/icons/npm.svg similarity index 100% rename from packages/interface/src/assets/icons/npm.svg rename to packages/assets/icons/npm.svg diff --git a/packages/interface/src/assets/icons/nuxt.svg b/packages/assets/icons/nuxt.svg similarity index 100% rename from packages/interface/src/assets/icons/nuxt.svg rename to packages/assets/icons/nuxt.svg diff --git a/packages/interface/src/assets/icons/nvm.svg b/packages/assets/icons/nvm.svg similarity index 100% rename from packages/interface/src/assets/icons/nvm.svg rename to packages/assets/icons/nvm.svg diff --git a/packages/interface/src/assets/icons/opengl.svg b/packages/assets/icons/opengl.svg similarity index 100% rename from packages/interface/src/assets/icons/opengl.svg rename to packages/assets/icons/opengl.svg diff --git a/packages/interface/src/assets/icons/pdf.svg b/packages/assets/icons/pdf.svg similarity index 100% rename from packages/interface/src/assets/icons/pdf.svg rename to packages/assets/icons/pdf.svg diff --git a/packages/interface/src/assets/icons/photoshop.svg b/packages/assets/icons/photoshop.svg similarity index 100% rename from packages/interface/src/assets/icons/photoshop.svg rename to packages/assets/icons/photoshop.svg diff --git a/packages/interface/src/assets/icons/php.svg b/packages/assets/icons/php.svg similarity index 100% rename from packages/interface/src/assets/icons/php.svg rename to packages/assets/icons/php.svg diff --git a/packages/interface/src/assets/icons/postcss-config.svg b/packages/assets/icons/postcss-config.svg similarity index 100% rename from packages/interface/src/assets/icons/postcss-config.svg rename to packages/assets/icons/postcss-config.svg diff --git a/packages/interface/src/assets/icons/powershell-data.svg b/packages/assets/icons/powershell-data.svg similarity index 100% rename from packages/interface/src/assets/icons/powershell-data.svg rename to packages/assets/icons/powershell-data.svg diff --git a/packages/interface/src/assets/icons/powershell-module.svg b/packages/assets/icons/powershell-module.svg similarity index 100% rename from packages/interface/src/assets/icons/powershell-module.svg rename to packages/assets/icons/powershell-module.svg diff --git a/packages/interface/src/assets/icons/powershell.svg b/packages/assets/icons/powershell.svg similarity index 100% rename from packages/interface/src/assets/icons/powershell.svg rename to packages/assets/icons/powershell.svg diff --git a/packages/interface/src/assets/icons/prettier.svg b/packages/assets/icons/prettier.svg similarity index 100% rename from packages/interface/src/assets/icons/prettier.svg rename to packages/assets/icons/prettier.svg diff --git a/packages/interface/src/assets/icons/prisma.svg b/packages/assets/icons/prisma.svg similarity index 100% rename from packages/interface/src/assets/icons/prisma.svg rename to packages/assets/icons/prisma.svg diff --git a/packages/interface/src/assets/icons/prolog.svg b/packages/assets/icons/prolog.svg similarity index 100% rename from packages/interface/src/assets/icons/prolog.svg rename to packages/assets/icons/prolog.svg diff --git a/packages/interface/src/assets/icons/pug.svg b/packages/assets/icons/pug.svg similarity index 100% rename from packages/interface/src/assets/icons/pug.svg rename to packages/assets/icons/pug.svg diff --git a/packages/interface/src/assets/icons/python.svg b/packages/assets/icons/python.svg similarity index 100% rename from packages/interface/src/assets/icons/python.svg rename to packages/assets/icons/python.svg diff --git a/packages/interface/src/assets/icons/qt.svg b/packages/assets/icons/qt.svg similarity index 100% rename from packages/interface/src/assets/icons/qt.svg rename to packages/assets/icons/qt.svg diff --git a/packages/interface/src/assets/icons/razor.svg b/packages/assets/icons/razor.svg similarity index 100% rename from packages/interface/src/assets/icons/razor.svg rename to packages/assets/icons/razor.svg diff --git a/packages/interface/src/assets/icons/react-js.svg b/packages/assets/icons/react-js.svg similarity index 100% rename from packages/interface/src/assets/icons/react-js.svg rename to packages/assets/icons/react-js.svg diff --git a/packages/interface/src/assets/icons/react-ts.svg b/packages/assets/icons/react-ts.svg similarity index 100% rename from packages/interface/src/assets/icons/react-ts.svg rename to packages/assets/icons/react-ts.svg diff --git a/packages/interface/src/assets/icons/readme.svg b/packages/assets/icons/readme.svg similarity index 100% rename from packages/interface/src/assets/icons/readme.svg rename to packages/assets/icons/readme.svg diff --git a/packages/interface/src/assets/icons/rescript.svg b/packages/assets/icons/rescript.svg similarity index 100% rename from packages/interface/src/assets/icons/rescript.svg rename to packages/assets/icons/rescript.svg diff --git a/packages/interface/src/assets/icons/rjson.svg b/packages/assets/icons/rjson.svg similarity index 100% rename from packages/interface/src/assets/icons/rjson.svg rename to packages/assets/icons/rjson.svg diff --git a/packages/interface/src/assets/icons/robots.svg b/packages/assets/icons/robots.svg similarity index 100% rename from packages/interface/src/assets/icons/robots.svg rename to packages/assets/icons/robots.svg diff --git a/packages/interface/src/assets/icons/rollup.svg b/packages/assets/icons/rollup.svg similarity index 100% rename from packages/interface/src/assets/icons/rollup.svg rename to packages/assets/icons/rollup.svg diff --git a/packages/interface/src/assets/icons/ruby.svg b/packages/assets/icons/ruby.svg similarity index 100% rename from packages/interface/src/assets/icons/ruby.svg rename to packages/assets/icons/ruby.svg diff --git a/packages/interface/src/assets/icons/rust.svg b/packages/assets/icons/rust.svg similarity index 100% rename from packages/interface/src/assets/icons/rust.svg rename to packages/assets/icons/rust.svg diff --git a/packages/interface/src/assets/icons/sass.svg b/packages/assets/icons/sass.svg similarity index 100% rename from packages/interface/src/assets/icons/sass.svg rename to packages/assets/icons/sass.svg diff --git a/packages/interface/src/assets/icons/scss.svg b/packages/assets/icons/scss.svg similarity index 100% rename from packages/interface/src/assets/icons/scss.svg rename to packages/assets/icons/scss.svg diff --git a/packages/interface/src/assets/icons/shell.svg b/packages/assets/icons/shell.svg similarity index 100% rename from packages/interface/src/assets/icons/shell.svg rename to packages/assets/icons/shell.svg diff --git a/packages/interface/src/assets/icons/smarty.svg b/packages/assets/icons/smarty.svg similarity index 100% rename from packages/interface/src/assets/icons/smarty.svg rename to packages/assets/icons/smarty.svg diff --git a/packages/interface/src/assets/icons/sol.svg b/packages/assets/icons/sol.svg similarity index 100% rename from packages/interface/src/assets/icons/sol.svg rename to packages/assets/icons/sol.svg diff --git a/packages/interface/src/assets/icons/sql.svg b/packages/assets/icons/sql.svg similarity index 100% rename from packages/interface/src/assets/icons/sql.svg rename to packages/assets/icons/sql.svg diff --git a/packages/interface/src/assets/icons/storybook.svg b/packages/assets/icons/storybook.svg similarity index 100% rename from packages/interface/src/assets/icons/storybook.svg rename to packages/assets/icons/storybook.svg diff --git a/packages/interface/src/assets/icons/stylelint.svg b/packages/assets/icons/stylelint.svg similarity index 100% rename from packages/interface/src/assets/icons/stylelint.svg rename to packages/assets/icons/stylelint.svg diff --git a/packages/interface/src/assets/icons/stylus.svg b/packages/assets/icons/stylus.svg similarity index 100% rename from packages/interface/src/assets/icons/stylus.svg rename to packages/assets/icons/stylus.svg diff --git a/packages/interface/src/assets/icons/svelte.svg b/packages/assets/icons/svelte.svg similarity index 100% rename from packages/interface/src/assets/icons/svelte.svg rename to packages/assets/icons/svelte.svg diff --git a/packages/interface/src/assets/icons/svg.svg b/packages/assets/icons/svg.svg similarity index 100% rename from packages/interface/src/assets/icons/svg.svg rename to packages/assets/icons/svg.svg diff --git a/packages/interface/src/assets/icons/swift.svg b/packages/assets/icons/swift.svg similarity index 100% rename from packages/interface/src/assets/icons/swift.svg rename to packages/assets/icons/swift.svg diff --git a/packages/interface/src/assets/icons/symfony.svg b/packages/assets/icons/symfony.svg similarity index 100% rename from packages/interface/src/assets/icons/symfony.svg rename to packages/assets/icons/symfony.svg diff --git a/packages/interface/src/assets/icons/tailwind.svg b/packages/assets/icons/tailwind.svg similarity index 100% rename from packages/interface/src/assets/icons/tailwind.svg rename to packages/assets/icons/tailwind.svg diff --git a/packages/interface/src/assets/icons/test-js.svg b/packages/assets/icons/test-js.svg similarity index 100% rename from packages/interface/src/assets/icons/test-js.svg rename to packages/assets/icons/test-js.svg diff --git a/packages/interface/src/assets/icons/test-ts.svg b/packages/assets/icons/test-ts.svg similarity index 100% rename from packages/interface/src/assets/icons/test-ts.svg rename to packages/assets/icons/test-ts.svg diff --git a/packages/interface/src/assets/icons/tmpl.svg b/packages/assets/icons/tmpl.svg similarity index 100% rename from packages/interface/src/assets/icons/tmpl.svg rename to packages/assets/icons/tmpl.svg diff --git a/packages/interface/src/assets/icons/toml.svg b/packages/assets/icons/toml.svg similarity index 100% rename from packages/interface/src/assets/icons/toml.svg rename to packages/assets/icons/toml.svg diff --git a/packages/interface/src/assets/icons/travis.svg b/packages/assets/icons/travis.svg similarity index 100% rename from packages/interface/src/assets/icons/travis.svg rename to packages/assets/icons/travis.svg diff --git a/packages/interface/src/assets/icons/tsconfig.svg b/packages/assets/icons/tsconfig.svg similarity index 100% rename from packages/interface/src/assets/icons/tsconfig.svg rename to packages/assets/icons/tsconfig.svg diff --git a/packages/interface/src/assets/icons/tsx.svg b/packages/assets/icons/tsx.svg similarity index 100% rename from packages/interface/src/assets/icons/tsx.svg rename to packages/assets/icons/tsx.svg diff --git a/packages/interface/src/assets/icons/twig.svg b/packages/assets/icons/twig.svg similarity index 100% rename from packages/interface/src/assets/icons/twig.svg rename to packages/assets/icons/twig.svg diff --git a/packages/interface/src/assets/icons/txt.svg b/packages/assets/icons/txt.svg similarity index 100% rename from packages/interface/src/assets/icons/txt.svg rename to packages/assets/icons/txt.svg diff --git a/packages/interface/src/assets/icons/typescript-def.svg b/packages/assets/icons/typescript-def.svg similarity index 100% rename from packages/interface/src/assets/icons/typescript-def.svg rename to packages/assets/icons/typescript-def.svg diff --git a/packages/interface/src/assets/icons/typescript.svg b/packages/assets/icons/typescript.svg similarity index 100% rename from packages/interface/src/assets/icons/typescript.svg rename to packages/assets/icons/typescript.svg diff --git a/packages/interface/src/assets/icons/ui.svg b/packages/assets/icons/ui.svg similarity index 100% rename from packages/interface/src/assets/icons/ui.svg rename to packages/assets/icons/ui.svg diff --git a/packages/interface/src/assets/icons/user.svg b/packages/assets/icons/user.svg similarity index 100% rename from packages/interface/src/assets/icons/user.svg rename to packages/assets/icons/user.svg diff --git a/packages/interface/src/assets/icons/vercel.svg b/packages/assets/icons/vercel.svg similarity index 100% rename from packages/interface/src/assets/icons/vercel.svg rename to packages/assets/icons/vercel.svg diff --git a/packages/interface/src/assets/icons/video.svg b/packages/assets/icons/video.svg similarity index 100% rename from packages/interface/src/assets/icons/video.svg rename to packages/assets/icons/video.svg diff --git a/packages/interface/src/assets/icons/vite.svg b/packages/assets/icons/vite.svg similarity index 100% rename from packages/interface/src/assets/icons/vite.svg rename to packages/assets/icons/vite.svg diff --git a/packages/interface/src/assets/icons/vscode.svg b/packages/assets/icons/vscode.svg similarity index 100% rename from packages/interface/src/assets/icons/vscode.svg rename to packages/assets/icons/vscode.svg diff --git a/packages/interface/src/assets/icons/vue.svg b/packages/assets/icons/vue.svg similarity index 100% rename from packages/interface/src/assets/icons/vue.svg rename to packages/assets/icons/vue.svg diff --git a/packages/interface/src/assets/icons/wasm.svg b/packages/assets/icons/wasm.svg similarity index 100% rename from packages/interface/src/assets/icons/wasm.svg rename to packages/assets/icons/wasm.svg diff --git a/packages/interface/src/assets/icons/webpack.svg b/packages/assets/icons/webpack.svg similarity index 100% rename from packages/interface/src/assets/icons/webpack.svg rename to packages/assets/icons/webpack.svg diff --git a/packages/interface/src/assets/icons/windi.svg b/packages/assets/icons/windi.svg similarity index 100% rename from packages/interface/src/assets/icons/windi.svg rename to packages/assets/icons/windi.svg diff --git a/packages/interface/src/assets/icons/xml.svg b/packages/assets/icons/xml.svg similarity index 100% rename from packages/interface/src/assets/icons/xml.svg rename to packages/assets/icons/xml.svg diff --git a/packages/interface/src/assets/icons/yaml.svg b/packages/assets/icons/yaml.svg similarity index 100% rename from packages/interface/src/assets/icons/yaml.svg rename to packages/assets/icons/yaml.svg diff --git a/packages/interface/src/assets/icons/yarn-error.svg b/packages/assets/icons/yarn-error.svg similarity index 100% rename from packages/interface/src/assets/icons/yarn-error.svg rename to packages/assets/icons/yarn-error.svg diff --git a/packages/interface/src/assets/icons/yarn.svg b/packages/assets/icons/yarn.svg similarity index 100% rename from packages/interface/src/assets/icons/yarn.svg rename to packages/assets/icons/yarn.svg diff --git a/packages/interface/src/assets/icons/zip.svg b/packages/assets/icons/zip.svg similarity index 100% rename from packages/interface/src/assets/icons/zip.svg rename to packages/assets/icons/zip.svg diff --git a/packages/interface/src/assets/images/spacedrive_logo.png b/packages/assets/images/spacedrive_logo.png similarity index 100% rename from packages/interface/src/assets/images/spacedrive_logo.png rename to packages/assets/images/spacedrive_logo.png diff --git a/packages/assets/package.json b/packages/assets/package.json new file mode 100644 index 000000000..6e1ae8c0a --- /dev/null +++ b/packages/assets/package.json @@ -0,0 +1,6 @@ +{ + "name": "@sd/assets", + "version": "1.0.0", + "license": "GPL-3.0-only", + "private": true +} diff --git a/packages/interface/package.json b/packages/interface/package.json index 709290266..66f7652e2 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -26,6 +26,7 @@ "@sd/client": "workspace:*", "@sd/core": "workspace:*", "@sd/ui": "workspace:*", + "@sd/assets": "workspace:*", "@types/styled-components": "^5.1.25", "@vitejs/plugin-react": "^1.3.2", "autoprefixer": "^10.4.7", diff --git a/packages/interface/scripts/generateSvgImports.mjs b/packages/interface/scripts/generateSvgImports.mjs index e9e6144f6..8f0d83436 100755 --- a/packages/interface/scripts/generateSvgImports.mjs +++ b/packages/interface/scripts/generateSvgImports.mjs @@ -32,14 +32,15 @@ async function exists(path) { } } +// TODO: Do the same for mobile app too, potentially with the 1 script. (async function main() { - const files = await fs.readdir('./packages/interface/src/assets/icons'); + const files = await fs.readdir('./packages/assets/icons'); const icons = files.filter((path) => path.endsWith('.svg')); const generatedCode = `\ ${icons .map((path) => iconBaseName(path)) - .map((baseName) => `import { ReactComponent as ${iconFriendlyName(baseName)} } from './${baseName}.svg';`) + .map((baseName) => `import { ReactComponent as ${iconFriendlyName(baseName)} } from '@sd/assets/icons/${baseName}.svg';`) .join('\n')} export default { diff --git a/packages/interface/src/assets/icons/index.ts b/packages/interface/src/assets/icons/index.ts index cb06aec56..7235bd1fa 100644 --- a/packages/interface/src/assets/icons/index.ts +++ b/packages/interface/src/assets/icons/index.ts @@ -1,175 +1,175 @@ -import { ReactComponent as ai } from './ai.svg'; -import { ReactComponent as angular } from './angular.svg'; -import { ReactComponent as audiomp3 } from './audio-mp3.svg'; -import { ReactComponent as audioogg } from './audio-ogg.svg'; -import { ReactComponent as audiowav } from './audio-wav.svg'; -import { ReactComponent as audio } from './audio.svg'; -import { ReactComponent as babel } from './babel.svg'; -import { ReactComponent as bat } from './bat.svg'; -import { ReactComponent as bicep } from './bicep.svg'; -import { ReactComponent as binary } from './binary.svg'; -import { ReactComponent as blade } from './blade.svg'; -import { ReactComponent as browserslist } from './browserslist.svg'; -import { ReactComponent as bsconfig } from './bsconfig.svg'; -import { ReactComponent as bundler } from './bundler.svg'; -import { ReactComponent as c } from './c.svg'; -import { ReactComponent as cert } from './cert.svg'; -import { ReactComponent as cheader } from './cheader.svg'; -import { ReactComponent as cli } from './cli.svg'; -import { ReactComponent as compodoc } from './compodoc.svg'; -import { ReactComponent as composer } from './composer.svg'; -import { ReactComponent as conf } from './conf.svg'; -import { ReactComponent as cpp } from './cpp.svg'; -import { ReactComponent as csharp } from './csharp.svg'; -import { ReactComponent as cshtml } from './cshtml.svg'; -import { ReactComponent as cssmap } from './css-map.svg'; -import { ReactComponent as css } from './css.svg'; -import { ReactComponent as csv } from './csv.svg'; -import { ReactComponent as dartlang } from './dartlang.svg'; -import { ReactComponent as dockerdebug } from './docker-debug.svg'; -import { ReactComponent as dockerignore } from './docker-ignore.svg'; -import { ReactComponent as docker } from './docker.svg'; -import { ReactComponent as editorconfig } from './editorconfig.svg'; -import { ReactComponent as eex } from './eex.svg'; -import { ReactComponent as elixir } from './elixir.svg'; -import { ReactComponent as elm } from './elm.svg'; -import { ReactComponent as env } from './env.svg'; -import { ReactComponent as erb } from './erb.svg'; -import { ReactComponent as erlang } from './erlang.svg'; -import { ReactComponent as eslint } from './eslint.svg'; -import { ReactComponent as exs } from './exs.svg'; -import { ReactComponent as exx } from './exx.svg'; -import { ReactComponent as file } from './file.svg'; -import { ReactComponent as folderlight } from './folder-light.svg'; -import { ReactComponent as folderopen } from './folder-open.svg'; -import { ReactComponent as folder } from './folder.svg'; -import { ReactComponent as fontotf } from './font-otf.svg'; -import { ReactComponent as fontttf } from './font-ttf.svg'; -import { ReactComponent as fontwoff2 } from './font-woff2.svg'; -import { ReactComponent as fontwoff } from './font-woff.svg'; -import { ReactComponent as git } from './git.svg'; -import { ReactComponent as gopackage } from './go-package.svg'; -import { ReactComponent as go } from './go.svg'; -import { ReactComponent as gradle } from './gradle.svg'; -import { ReactComponent as graphql } from './graphql.svg'; -import { ReactComponent as groovy } from './groovy.svg'; -import { ReactComponent as grunt } from './grunt.svg'; -import { ReactComponent as gulp } from './gulp.svg'; -import { ReactComponent as haml } from './haml.svg'; -import { ReactComponent as handlebars } from './handlebars.svg'; -import { ReactComponent as haskell } from './haskell.svg'; -import { ReactComponent as html } from './html.svg'; -import { ReactComponent as imagegif } from './image-gif.svg'; -import { ReactComponent as imageico } from './image-ico.svg'; -import { ReactComponent as imagejpg } from './image-jpg.svg'; -import { ReactComponent as imagepng } from './image-png.svg'; -import { ReactComponent as imagewebp } from './image-webp.svg'; -import { ReactComponent as image } from './image.svg'; -import { ReactComponent as info } from './info.svg'; -import { ReactComponent as ipynb } from './ipynb.svg'; -import { ReactComponent as java } from './java.svg'; -import { ReactComponent as jenkins } from './jenkins.svg'; -import { ReactComponent as jest } from './jest.svg'; -import { ReactComponent as jinja } from './jinja.svg'; -import { ReactComponent as jsmap } from './js-map.svg'; -import { ReactComponent as js } from './js.svg'; -import { ReactComponent as json } from './json.svg'; -import { ReactComponent as jsp } from './jsp.svg'; -import { ReactComponent as julia } from './julia.svg'; -import { ReactComponent as karma } from './karma.svg'; -import { ReactComponent as key } from './key.svg'; -import { ReactComponent as less } from './less.svg'; -import { ReactComponent as license } from './license.svg'; -import { ReactComponent as lighteditorconfig } from './lighteditorconfig.svg'; -import { ReactComponent as liquid } from './liquid.svg'; -import { ReactComponent as llvm } from './llvm.svg'; -import { ReactComponent as log } from './log.svg'; -import { ReactComponent as lua } from './lua.svg'; -import { ReactComponent as m } from './m.svg'; -import { ReactComponent as markdown } from './markdown.svg'; -import { ReactComponent as mint } from './mint.svg'; -import { ReactComponent as mov } from './mov.svg'; -import { ReactComponent as mp4 } from './mp4.svg'; -import { ReactComponent as nestjscontroller } from './nestjs-controller.svg'; -import { ReactComponent as nestjsdecorator } from './nestjs-decorator.svg'; -import { ReactComponent as nestjsfilter } from './nestjs-filter.svg'; -import { ReactComponent as nestjsguard } from './nestjs-guard.svg'; -import { ReactComponent as nestjsmodule } from './nestjs-module.svg'; -import { ReactComponent as nestjsservice } from './nestjs-service.svg'; -import { ReactComponent as nestjs } from './nestjs.svg'; -import { ReactComponent as netlify } from './netlify.svg'; -import { ReactComponent as nginx } from './nginx.svg'; -import { ReactComponent as nim } from './nim.svg'; -import { ReactComponent as njk } from './njk.svg'; -import { ReactComponent as nodemon } from './nodemon.svg'; -import { ReactComponent as npmlock } from './npm-lock.svg'; -import { ReactComponent as npm } from './npm.svg'; -import { ReactComponent as nuxt } from './nuxt.svg'; -import { ReactComponent as nvm } from './nvm.svg'; -import { ReactComponent as opengl } from './opengl.svg'; -import { ReactComponent as pdf } from './pdf.svg'; -import { ReactComponent as photoshop } from './photoshop.svg'; -import { ReactComponent as php } from './php.svg'; -import { ReactComponent as postcssconfig } from './postcss-config.svg'; -import { ReactComponent as powershelldata } from './powershell-data.svg'; -import { ReactComponent as powershellmodule } from './powershell-module.svg'; -import { ReactComponent as powershell } from './powershell.svg'; -import { ReactComponent as prettier } from './prettier.svg'; -import { ReactComponent as prisma } from './prisma.svg'; -import { ReactComponent as prolog } from './prolog.svg'; -import { ReactComponent as pug } from './pug.svg'; -import { ReactComponent as python } from './python.svg'; -import { ReactComponent as qt } from './qt.svg'; -import { ReactComponent as razor } from './razor.svg'; -import { ReactComponent as reactjs } from './react-js.svg'; -import { ReactComponent as reactts } from './react-ts.svg'; -import { ReactComponent as readme } from './readme.svg'; -import { ReactComponent as rescript } from './rescript.svg'; -import { ReactComponent as rjson } from './rjson.svg'; -import { ReactComponent as robots } from './robots.svg'; -import { ReactComponent as rollup } from './rollup.svg'; -import { ReactComponent as ruby } from './ruby.svg'; -import { ReactComponent as rust } from './rust.svg'; -import { ReactComponent as sass } from './sass.svg'; -import { ReactComponent as scss } from './scss.svg'; -import { ReactComponent as shell } from './shell.svg'; -import { ReactComponent as smarty } from './smarty.svg'; -import { ReactComponent as sol } from './sol.svg'; -import { ReactComponent as sql } from './sql.svg'; -import { ReactComponent as storybook } from './storybook.svg'; -import { ReactComponent as stylelint } from './stylelint.svg'; -import { ReactComponent as stylus } from './stylus.svg'; -import { ReactComponent as svelte } from './svelte.svg'; -import { ReactComponent as svg } from './svg.svg'; -import { ReactComponent as swift } from './swift.svg'; -import { ReactComponent as symfony } from './symfony.svg'; -import { ReactComponent as tailwind } from './tailwind.svg'; -import { ReactComponent as testjs } from './test-js.svg'; -import { ReactComponent as testts } from './test-ts.svg'; -import { ReactComponent as tmpl } from './tmpl.svg'; -import { ReactComponent as toml } from './toml.svg'; -import { ReactComponent as travis } from './travis.svg'; -import { ReactComponent as tsconfig } from './tsconfig.svg'; -import { ReactComponent as tsx } from './tsx.svg'; -import { ReactComponent as twig } from './twig.svg'; -import { ReactComponent as txt } from './txt.svg'; -import { ReactComponent as typescriptdef } from './typescript-def.svg'; -import { ReactComponent as typescript } from './typescript.svg'; -import { ReactComponent as ui } from './ui.svg'; -import { ReactComponent as user } from './user.svg'; -import { ReactComponent as vercel } from './vercel.svg'; -import { ReactComponent as video } from './video.svg'; -import { ReactComponent as vite } from './vite.svg'; -import { ReactComponent as vscode } from './vscode.svg'; -import { ReactComponent as vue } from './vue.svg'; -import { ReactComponent as wasm } from './wasm.svg'; -import { ReactComponent as webpack } from './webpack.svg'; -import { ReactComponent as windi } from './windi.svg'; -import { ReactComponent as xml } from './xml.svg'; -import { ReactComponent as yaml } from './yaml.svg'; -import { ReactComponent as yarnerror } from './yarn-error.svg'; -import { ReactComponent as yarn } from './yarn.svg'; -import { ReactComponent as zip } from './zip.svg'; +import { ReactComponent as ai } from '@sd/assets/icons/ai.svg'; +import { ReactComponent as angular } from '@sd/assets/icons/angular.svg'; +import { ReactComponent as audiomp3 } from '@sd/assets/icons/audio-mp3.svg'; +import { ReactComponent as audioogg } from '@sd/assets/icons/audio-ogg.svg'; +import { ReactComponent as audiowav } from '@sd/assets/icons/audio-wav.svg'; +import { ReactComponent as audio } from '@sd/assets/icons/audio.svg'; +import { ReactComponent as babel } from '@sd/assets/icons/babel.svg'; +import { ReactComponent as bat } from '@sd/assets/icons/bat.svg'; +import { ReactComponent as bicep } from '@sd/assets/icons/bicep.svg'; +import { ReactComponent as binary } from '@sd/assets/icons/binary.svg'; +import { ReactComponent as blade } from '@sd/assets/icons/blade.svg'; +import { ReactComponent as browserslist } from '@sd/assets/icons/browserslist.svg'; +import { ReactComponent as bsconfig } from '@sd/assets/icons/bsconfig.svg'; +import { ReactComponent as bundler } from '@sd/assets/icons/bundler.svg'; +import { ReactComponent as c } from '@sd/assets/icons/c.svg'; +import { ReactComponent as cert } from '@sd/assets/icons/cert.svg'; +import { ReactComponent as cheader } from '@sd/assets/icons/cheader.svg'; +import { ReactComponent as cli } from '@sd/assets/icons/cli.svg'; +import { ReactComponent as compodoc } from '@sd/assets/icons/compodoc.svg'; +import { ReactComponent as composer } from '@sd/assets/icons/composer.svg'; +import { ReactComponent as conf } from '@sd/assets/icons/conf.svg'; +import { ReactComponent as cpp } from '@sd/assets/icons/cpp.svg'; +import { ReactComponent as csharp } from '@sd/assets/icons/csharp.svg'; +import { ReactComponent as cshtml } from '@sd/assets/icons/cshtml.svg'; +import { ReactComponent as cssmap } from '@sd/assets/icons/css-map.svg'; +import { ReactComponent as css } from '@sd/assets/icons/css.svg'; +import { ReactComponent as csv } from '@sd/assets/icons/csv.svg'; +import { ReactComponent as dartlang } from '@sd/assets/icons/dartlang.svg'; +import { ReactComponent as dockerdebug } from '@sd/assets/icons/docker-debug.svg'; +import { ReactComponent as dockerignore } from '@sd/assets/icons/docker-ignore.svg'; +import { ReactComponent as docker } from '@sd/assets/icons/docker.svg'; +import { ReactComponent as editorconfig } from '@sd/assets/icons/editorconfig.svg'; +import { ReactComponent as eex } from '@sd/assets/icons/eex.svg'; +import { ReactComponent as elixir } from '@sd/assets/icons/elixir.svg'; +import { ReactComponent as elm } from '@sd/assets/icons/elm.svg'; +import { ReactComponent as env } from '@sd/assets/icons/env.svg'; +import { ReactComponent as erb } from '@sd/assets/icons/erb.svg'; +import { ReactComponent as erlang } from '@sd/assets/icons/erlang.svg'; +import { ReactComponent as eslint } from '@sd/assets/icons/eslint.svg'; +import { ReactComponent as exs } from '@sd/assets/icons/exs.svg'; +import { ReactComponent as exx } from '@sd/assets/icons/exx.svg'; +import { ReactComponent as file } from '@sd/assets/icons/file.svg'; +import { ReactComponent as folderlight } from '@sd/assets/icons/folder-light.svg'; +import { ReactComponent as folderopen } from '@sd/assets/icons/folder-open.svg'; +import { ReactComponent as folder } from '@sd/assets/icons/folder.svg'; +import { ReactComponent as fontotf } from '@sd/assets/icons/font-otf.svg'; +import { ReactComponent as fontttf } from '@sd/assets/icons/font-ttf.svg'; +import { ReactComponent as fontwoff2 } from '@sd/assets/icons/font-woff2.svg'; +import { ReactComponent as fontwoff } from '@sd/assets/icons/font-woff.svg'; +import { ReactComponent as git } from '@sd/assets/icons/git.svg'; +import { ReactComponent as gopackage } from '@sd/assets/icons/go-package.svg'; +import { ReactComponent as go } from '@sd/assets/icons/go.svg'; +import { ReactComponent as gradle } from '@sd/assets/icons/gradle.svg'; +import { ReactComponent as graphql } from '@sd/assets/icons/graphql.svg'; +import { ReactComponent as groovy } from '@sd/assets/icons/groovy.svg'; +import { ReactComponent as grunt } from '@sd/assets/icons/grunt.svg'; +import { ReactComponent as gulp } from '@sd/assets/icons/gulp.svg'; +import { ReactComponent as haml } from '@sd/assets/icons/haml.svg'; +import { ReactComponent as handlebars } from '@sd/assets/icons/handlebars.svg'; +import { ReactComponent as haskell } from '@sd/assets/icons/haskell.svg'; +import { ReactComponent as html } from '@sd/assets/icons/html.svg'; +import { ReactComponent as imagegif } from '@sd/assets/icons/image-gif.svg'; +import { ReactComponent as imageico } from '@sd/assets/icons/image-ico.svg'; +import { ReactComponent as imagejpg } from '@sd/assets/icons/image-jpg.svg'; +import { ReactComponent as imagepng } from '@sd/assets/icons/image-png.svg'; +import { ReactComponent as imagewebp } from '@sd/assets/icons/image-webp.svg'; +import { ReactComponent as image } from '@sd/assets/icons/image.svg'; +import { ReactComponent as info } from '@sd/assets/icons/info.svg'; +import { ReactComponent as ipynb } from '@sd/assets/icons/ipynb.svg'; +import { ReactComponent as java } from '@sd/assets/icons/java.svg'; +import { ReactComponent as jenkins } from '@sd/assets/icons/jenkins.svg'; +import { ReactComponent as jest } from '@sd/assets/icons/jest.svg'; +import { ReactComponent as jinja } from '@sd/assets/icons/jinja.svg'; +import { ReactComponent as jsmap } from '@sd/assets/icons/js-map.svg'; +import { ReactComponent as js } from '@sd/assets/icons/js.svg'; +import { ReactComponent as json } from '@sd/assets/icons/json.svg'; +import { ReactComponent as jsp } from '@sd/assets/icons/jsp.svg'; +import { ReactComponent as julia } from '@sd/assets/icons/julia.svg'; +import { ReactComponent as karma } from '@sd/assets/icons/karma.svg'; +import { ReactComponent as key } from '@sd/assets/icons/key.svg'; +import { ReactComponent as less } from '@sd/assets/icons/less.svg'; +import { ReactComponent as license } from '@sd/assets/icons/license.svg'; +import { ReactComponent as lighteditorconfig } from '@sd/assets/icons/lighteditorconfig.svg'; +import { ReactComponent as liquid } from '@sd/assets/icons/liquid.svg'; +import { ReactComponent as llvm } from '@sd/assets/icons/llvm.svg'; +import { ReactComponent as log } from '@sd/assets/icons/log.svg'; +import { ReactComponent as lua } from '@sd/assets/icons/lua.svg'; +import { ReactComponent as m } from '@sd/assets/icons/m.svg'; +import { ReactComponent as markdown } from '@sd/assets/icons/markdown.svg'; +import { ReactComponent as mint } from '@sd/assets/icons/mint.svg'; +import { ReactComponent as mov } from '@sd/assets/icons/mov.svg'; +import { ReactComponent as mp4 } from '@sd/assets/icons/mp4.svg'; +import { ReactComponent as nestjscontroller } from '@sd/assets/icons/nestjs-controller.svg'; +import { ReactComponent as nestjsdecorator } from '@sd/assets/icons/nestjs-decorator.svg'; +import { ReactComponent as nestjsfilter } from '@sd/assets/icons/nestjs-filter.svg'; +import { ReactComponent as nestjsguard } from '@sd/assets/icons/nestjs-guard.svg'; +import { ReactComponent as nestjsmodule } from '@sd/assets/icons/nestjs-module.svg'; +import { ReactComponent as nestjsservice } from '@sd/assets/icons/nestjs-service.svg'; +import { ReactComponent as nestjs } from '@sd/assets/icons/nestjs.svg'; +import { ReactComponent as netlify } from '@sd/assets/icons/netlify.svg'; +import { ReactComponent as nginx } from '@sd/assets/icons/nginx.svg'; +import { ReactComponent as nim } from '@sd/assets/icons/nim.svg'; +import { ReactComponent as njk } from '@sd/assets/icons/njk.svg'; +import { ReactComponent as nodemon } from '@sd/assets/icons/nodemon.svg'; +import { ReactComponent as npmlock } from '@sd/assets/icons/npm-lock.svg'; +import { ReactComponent as npm } from '@sd/assets/icons/npm.svg'; +import { ReactComponent as nuxt } from '@sd/assets/icons/nuxt.svg'; +import { ReactComponent as nvm } from '@sd/assets/icons/nvm.svg'; +import { ReactComponent as opengl } from '@sd/assets/icons/opengl.svg'; +import { ReactComponent as pdf } from '@sd/assets/icons/pdf.svg'; +import { ReactComponent as photoshop } from '@sd/assets/icons/photoshop.svg'; +import { ReactComponent as php } from '@sd/assets/icons/php.svg'; +import { ReactComponent as postcssconfig } from '@sd/assets/icons/postcss-config.svg'; +import { ReactComponent as powershelldata } from '@sd/assets/icons/powershell-data.svg'; +import { ReactComponent as powershellmodule } from '@sd/assets/icons/powershell-module.svg'; +import { ReactComponent as powershell } from '@sd/assets/icons/powershell.svg'; +import { ReactComponent as prettier } from '@sd/assets/icons/prettier.svg'; +import { ReactComponent as prisma } from '@sd/assets/icons/prisma.svg'; +import { ReactComponent as prolog } from '@sd/assets/icons/prolog.svg'; +import { ReactComponent as pug } from '@sd/assets/icons/pug.svg'; +import { ReactComponent as python } from '@sd/assets/icons/python.svg'; +import { ReactComponent as qt } from '@sd/assets/icons/qt.svg'; +import { ReactComponent as razor } from '@sd/assets/icons/razor.svg'; +import { ReactComponent as reactjs } from '@sd/assets/icons/react-js.svg'; +import { ReactComponent as reactts } from '@sd/assets/icons/react-ts.svg'; +import { ReactComponent as readme } from '@sd/assets/icons/readme.svg'; +import { ReactComponent as rescript } from '@sd/assets/icons/rescript.svg'; +import { ReactComponent as rjson } from '@sd/assets/icons/rjson.svg'; +import { ReactComponent as robots } from '@sd/assets/icons/robots.svg'; +import { ReactComponent as rollup } from '@sd/assets/icons/rollup.svg'; +import { ReactComponent as ruby } from '@sd/assets/icons/ruby.svg'; +import { ReactComponent as rust } from '@sd/assets/icons/rust.svg'; +import { ReactComponent as sass } from '@sd/assets/icons/sass.svg'; +import { ReactComponent as scss } from '@sd/assets/icons/scss.svg'; +import { ReactComponent as shell } from '@sd/assets/icons/shell.svg'; +import { ReactComponent as smarty } from '@sd/assets/icons/smarty.svg'; +import { ReactComponent as sol } from '@sd/assets/icons/sol.svg'; +import { ReactComponent as sql } from '@sd/assets/icons/sql.svg'; +import { ReactComponent as storybook } from '@sd/assets/icons/storybook.svg'; +import { ReactComponent as stylelint } from '@sd/assets/icons/stylelint.svg'; +import { ReactComponent as stylus } from '@sd/assets/icons/stylus.svg'; +import { ReactComponent as svelte } from '@sd/assets/icons/svelte.svg'; +import { ReactComponent as svg } from '@sd/assets/icons/svg.svg'; +import { ReactComponent as swift } from '@sd/assets/icons/swift.svg'; +import { ReactComponent as symfony } from '@sd/assets/icons/symfony.svg'; +import { ReactComponent as tailwind } from '@sd/assets/icons/tailwind.svg'; +import { ReactComponent as testjs } from '@sd/assets/icons/test-js.svg'; +import { ReactComponent as testts } from '@sd/assets/icons/test-ts.svg'; +import { ReactComponent as tmpl } from '@sd/assets/icons/tmpl.svg'; +import { ReactComponent as toml } from '@sd/assets/icons/toml.svg'; +import { ReactComponent as travis } from '@sd/assets/icons/travis.svg'; +import { ReactComponent as tsconfig } from '@sd/assets/icons/tsconfig.svg'; +import { ReactComponent as tsx } from '@sd/assets/icons/tsx.svg'; +import { ReactComponent as twig } from '@sd/assets/icons/twig.svg'; +import { ReactComponent as txt } from '@sd/assets/icons/txt.svg'; +import { ReactComponent as typescriptdef } from '@sd/assets/icons/typescript-def.svg'; +import { ReactComponent as typescript } from '@sd/assets/icons/typescript.svg'; +import { ReactComponent as ui } from '@sd/assets/icons/ui.svg'; +import { ReactComponent as user } from '@sd/assets/icons/user.svg'; +import { ReactComponent as vercel } from '@sd/assets/icons/vercel.svg'; +import { ReactComponent as video } from '@sd/assets/icons/video.svg'; +import { ReactComponent as vite } from '@sd/assets/icons/vite.svg'; +import { ReactComponent as vscode } from '@sd/assets/icons/vscode.svg'; +import { ReactComponent as vue } from '@sd/assets/icons/vue.svg'; +import { ReactComponent as wasm } from '@sd/assets/icons/wasm.svg'; +import { ReactComponent as webpack } from '@sd/assets/icons/webpack.svg'; +import { ReactComponent as windi } from '@sd/assets/icons/windi.svg'; +import { ReactComponent as xml } from '@sd/assets/icons/xml.svg'; +import { ReactComponent as yaml } from '@sd/assets/icons/yaml.svg'; +import { ReactComponent as yarnerror } from '@sd/assets/icons/yarn-error.svg'; +import { ReactComponent as yarn } from '@sd/assets/icons/yarn.svg'; +import { ReactComponent as zip } from '@sd/assets/icons/zip.svg'; export default { ai, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84e1d945b4cca4c1cf2787dab4b852777543054f..59e2312b81c6db6fd68e6c3bdef88a7c5ec1099c 100644 GIT binary patch delta 370 zcmX?dQ0?(ywGCxbY>CCisU^jeXUZyWE|c2D0^=WGl$u=0Xty~}{uR48M2)(YLQZC0 zww0cqK2(#PxNLK#dV8iiBM>uf&s1mTE1JG1HUS>sKAn-s)7=;ywI#< zi!y`Y5=-+^FOz^sXEVR5l0whmM&h_`7UdYBQ-mX~0x?Qn|t=k1`W&7D}Y(UJu{p>c5?=Jzw8iSPp delta 213 zcmV;`04o3E!zRSRCa`uIvz;2c1e19IK9kx38ne$I&Ny#shvJ9WgCyVt6ofQZ{Qs zVtGnZNjEibS7tU^O*E51I~A9Has>y6FLMRAFLMTjM3=^m1R;m) Pw+4sow+FZEw+Q#?tMpK{ From 1d30850c21c73e1bb798bcac98b2f959f1a64250 Mon Sep 17 00:00:00 2001 From: "Ericson \"Fogo\" Soares" Date: Wed, 27 Jul 2022 04:06:34 -0300 Subject: [PATCH 25/51] Resumable Jobs + Lefthook Integration (#344) * Introducing Lefthook for git hooks automation * TypeScript typechecking and linting * Rust formatting and linting * Spellchecking (also corrected spell errors in some files) * Check links in md files * Introducing resumable jobs * Abstractions to pause and resume jobs automatically when application exits and is started * Changing database to use Bytes for UUID fields * Changing uuid fields on core to use uuid::Uuid instead of String * Updating some dependencies and introducing msg_pack serialization to save job state on database * Fixing some clippy warnings * Fixing a regression introduced on identifier job, doing too much db accesses concurrently --- .cspell/backend_custom_words.txt | 36 ++ .cspell/frontend_custom_words.txt | 82 ++++ .cspell/project_words.txt | 38 ++ .eslintrc.js | 21 + Cargo.lock | Bin 168085 -> 169141 bytes README.md | 4 +- apps/desktop/src-tauri/Cargo.toml | 2 + apps/desktop/src-tauri/src/main.rs | 67 ++- apps/desktop/src-tauri/src/menu.rs | 36 +- apps/server/src/main.rs | 25 +- core/Cargo.toml | 8 +- core/bindings/JobReport.ts | 2 +- core/bindings/JobStatus.ts | 2 +- .../migration.sql | 145 +++++++ core/prisma/schema.prisma | 22 +- core/src/encode/thumb.rs | 226 +++++++---- core/src/file/cas/identifier.rs | 363 ++++++++++------- core/src/file/indexer/mod.rs | 384 ++++++++++++++++-- core/src/file/indexer/scan.rs | 280 ------------- core/src/file/mod.rs | 39 +- core/src/job/{jobs.rs => job_manager.rs} | 148 +++++-- core/src/job/mod.rs | 185 ++++++++- core/src/job/worker.rs | 207 ++++++---- core/src/lib.rs | 133 ++++-- core/src/library/library_config.rs | 3 +- core/src/library/library_ctx.rs | 8 +- core/src/library/library_manager.rs | 37 +- core/src/library/mod.rs | 7 +- core/src/node/config.rs | 10 +- core/src/node/mod.rs | 6 +- core/src/sys/locations.rs | 62 +-- core/src/sys/mod.rs | 4 +- core/src/tag/mod.rs | 13 +- cspell.config.yaml | 20 + docs/architecture/database.md | 4 +- docs/architecture/distributed-data-sync.md | 4 +- docs/product/roadmap.md | 6 +- lefthook.yml | 33 ++ package.json | 13 + packages/interface/src/AppRouter.tsx | 4 +- .../src/components/primitive/Checkbox.tsx | 4 +- .../src/screens/settings/Settings.tsx | 4 +- ...indSettings.tsx => KeybindingSettings.tsx} | 4 +- pnpm-lock.yaml | Bin 631267 -> 672873 bytes 44 files changed, 1839 insertions(+), 862 deletions(-) create mode 100644 .cspell/backend_custom_words.txt create mode 100644 .cspell/frontend_custom_words.txt create mode 100644 .cspell/project_words.txt create mode 100644 .eslintrc.js create mode 100644 core/prisma/migrations/20220722181530_alter_uuids_to_bytes/migration.sql delete mode 100644 core/src/file/indexer/scan.rs rename core/src/job/{jobs.rs => job_manager.rs} (62%) create mode 100644 cspell.config.yaml create mode 100644 lefthook.yml rename packages/interface/src/screens/settings/client/{KeybindSettings.tsx => KeybindingSettings.tsx} (72%) diff --git a/.cspell/backend_custom_words.txt b/.cspell/backend_custom_words.txt new file mode 100644 index 000000000..4df68be4e --- /dev/null +++ b/.cspell/backend_custom_words.txt @@ -0,0 +1,36 @@ +tauri +rustup +aarch +sdcore +dotenv +dotenvy +prismjs +actix +rtype +healthcheck +sdserver +ipfs +impls +crdt +quicktime +creationdate +imageops +thumbnailer +HEXLOWER +chrono +walkdir +thiserror +thumbstrip +repr +Deque +oneshot +sdlibrary +sdconfig +DOTFILE +sysinfo +initialising +struct +UHLC +CRDTs +PRRTT +filesystems \ No newline at end of file diff --git a/.cspell/frontend_custom_words.txt b/.cspell/frontend_custom_words.txt new file mode 100644 index 000000000..34fcd1c69 --- /dev/null +++ b/.cspell/frontend_custom_words.txt @@ -0,0 +1,82 @@ +pnpm +titlebar +consts +pallete +unlisten +svgr +middlewares +clsx +SDWEB +tryghost +tsparticles +Opencollective +Waitlist +heroicons +roadmap +semibold +noreferer +Rescan +subpackage +photoslibrary +fontsource +audiomp +audioogg +audiowav +browserslist +bsconfig +cheader +compodoc +cssmap +dartlang +dockerdebug +folderlight +folderopen +fontotf +fontttf +fontwoff +gopackage +haml +imagegif +imageico +imagejpg +imagepng +ipynb +jsmap +lighteditorconfig +nestjscontroller +nestjs +nestjsdecorator +nestjsfilter +nestjsguard +nestjsmodule +nestjsservice +npmlock +nuxt +opengl +photoshop +postcssconfig +powershelldata +reactjs +rjson +symfony +testjs +tmpl +typescriptdef +windi +yarnerror +unlisten +imagewebp +powershellmodule +reactts +testts +zustand +overscan +webp +headlessui +falsey +nums +lacie +classname +wunsub +immer +tada \ No newline at end of file diff --git a/.cspell/project_words.txt b/.cspell/project_words.txt new file mode 100644 index 000000000..63d75c0f9 --- /dev/null +++ b/.cspell/project_words.txt @@ -0,0 +1,38 @@ +spacedrive +spacedriveapp +vdfs +haoyuan +brendonovich +codegen +elon +deel +haden +akar +benja +haris +mehrzad +OSSC +josephjacks +rauch +ravikant +neha +narkhede +allred +lütke +tobiaslutke +justinhoffman +rywalker +zacharysmith +sanjay +poonen +mytton +davidmytton +richelsen +lesterlee +alluxio +augusto +marietti +vijay +sharma +naveen +noco \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..f7d773462 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,21 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + "project": [ + "apps/desktop/tsconfig.json", + "apps/web/tsconfig.json", + "apps/landing/tsconfig.json", + "packages/client/tsconfig.json", + "packages/interface/tsconfig.json", + "packages/ui/tsconfig.json", + ], + }, + plugins: [ + '@typescript-eslint', + ], + extends: [ + 'standard-with-typescript', + 'prettier', + ], +}; \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2df75e10dd613bf927f9d4b93d9e7da2a8a6ad7d..e83b469b1900544cf3819025114489cbde755f96 100644 GIT binary patch delta 623 zcmY+CJ!q3r6o$FM2o0@bjiHK2nnp;eN$>v;qUokMWl zJ?zxHYlpc7xY4LA`@z!e!hHQqy+Px~PjvCD;n)Uh!Eh=JV?;ZwLh1-3t+OC8Xkm?4 z0Wyv?2u}&{V4QUvI~{l0rCMH{Y!>e>p_xux*+vIr|6FM*URXn8lXODs40ICl4y>0x z3s@z6Fxn8Ng9ojw!%oDFm#CJ%T{)6pyO<_Uqk8G<_F8jZ7e(B*h#4V^DW*3p6KYJc&+sJNp$Bp;vYbvYNr5Jj2L z!AU2y6w+!5nNA6_l#_6SE%Q1M?wIgSX$#mTCLA+s;`>i&&EB)&eBYPR5-dgdfsPLi z`it!)KX5*OH33~Jl$IHGR7pZGF(LqSGO)l1HOAnCTM-vBCoR=n;4~2$aPn8cxcWmP zS(NLIBpk^uY8J;{pf`sPrE-tVKfhw@HCh?kzlX%fsnrs60|Nq#aY6~vO87u1_Jk-8 zDmX!@7Aze@d4-t-rA%E delta 126 zcmdnGl56Tht_>0zleIM^o2@mrTWc^b(c6AJoY7iu`mA^v)rdzc$icU`dCqBKQm62y|@+@O4=k|>2j4LI#tA1phuowU>CoXpY diff --git a/README.md b/README.md index ae9203fd8..39333c6e5 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ For independent creatives, hoarders and those that want to own their digital foo # What is a VDFS? -A VDFS (virtual distributed filesystem) is a filesystem designed to work across a variety of storage layers. With a uniform API to manipulate and access content across many devices, VSFS is not restricted to a single machine. It achieves this by maintaining a virtual index of all storage locations, synchronizing the database between clients in realtime. This implementation also uses [CAS](https://en.wikipedia.org/wiki/Content-addressable_storage) (Content-addressable storage) to uniquely identify files, while keeping record of logical file paths relative to the storage locations. +A VDFS (virtual distributed filesystem) is a filesystem designed to work across a variety of storage layers. With a uniform API to manipulate and access content across many devices, VDFS is not restricted to a single machine. It achieves this by maintaining a virtual index of all storage locations, synchronizing the database between clients in realtime. This implementation also uses [CAS](https://en.wikipedia.org/wiki/Content-addressable_storage) (Content-addressable storage) to uniquely identify files, while keeping record of logical file paths relative to the storage locations. The first implementation of a VDFS can be found in this UC Berkeley [paper](https://www2.eecs.berkeley.edu/Pubs/TechRpts/2018/EECS-2018-29.pdf) by Haoyuan Li. This paper describes its use for cloud computing, however the underlying concepts can be translated to open consumer software. @@ -85,7 +85,7 @@ _Note: Links are for highlight purposes only until feature specific documentatio **To be developed (MVP):** - **[Photos](#features)** - Photo and video albums similar to Apple/Google photos. -- **[Search](#features)** - Deep search into your filesystem with a keybind, including offline locations. +- **[Search](#features)** - Deep search into your filesystem with a keybinding, including offline locations. - **[Tags](#features)** - Define routines on custom tags to automate workflows, easily tag files individually, in bulk and automatically via rules. - **[Extensions](#features)** - Build tools on top of Spacedrive, extend functionality and integrate third party services. Extension directory on [spacedrive.com/extensions](#features). diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index ab91a3359..ff2a9b3c2 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -20,9 +20,11 @@ sdcore = { path = "../../../core" } # Universal Dependencies tokio = { version = "1.17.0", features = ["sync"] } +futures = "^0.3" window-shadows = "0.1.2" env_logger = "0.9.0" dotenvy = "0.15.1" +log = { version = "0.4.17", features = ["max_level_trace"] } # macOS system libs [target.'cfg(target_os = "macos")'.dependencies] diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index 3c6210e46..67cce98ad 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -1,8 +1,13 @@ +use std::path::PathBuf; use std::time::{Duration, Instant}; use dotenvy::dotenv; +use futures::executor::block_on; +use log::{debug, error, info}; use sdcore::{ClientCommand, ClientQuery, CoreEvent, CoreResponse, Node, NodeController}; -use tauri::{api::path, Manager}; +use tauri::{api::path, Manager, RunEvent}; +use tokio::sync::oneshot; + #[cfg(target_os = "macos")] mod macos; mod menu; @@ -15,7 +20,7 @@ async fn client_query_transport( match core.query(data).await { Ok(response) => Ok(response), Err(err) => { - println!("query error: {:?}", err); + error!("query error: {:?}", err); Err(err.to_string()) } } @@ -42,18 +47,42 @@ async fn app_ready(app_handle: tauri::AppHandle) { window.show().unwrap(); } +struct ShutdownManager { + shutdown_tx: Option>, + shutdown_completion_rx: Option>, +} + +impl ShutdownManager { + fn shutdown(&mut self) { + if let Some(sender) = self.shutdown_tx.take() { + sender.send(()).unwrap(); + if let Some(receiver) = self.shutdown_completion_rx.take() { + block_on(receiver).expect("failed to receive shutdown completion signal"); + } + } + } +} + #[tokio::main] async fn main() { dotenv().ok(); env_logger::init(); - let mut data_dir = path::data_dir().unwrap_or(std::path::PathBuf::from("./")); - data_dir = data_dir.join("spacedrive"); + let data_dir = path::data_dir() + .unwrap_or_else(|| PathBuf::from("./")) + .join("spacedrive"); + // create an instance of the core - let (controller, mut event_receiver, node) = Node::new(data_dir).await; - tokio::spawn(node.start()); + let (controller, mut event_receiver, node, shutdown_completion_rx) = Node::new(data_dir).await; + let (shutdown_tx, shutdown_rx) = oneshot::channel(); + let mut shutdown_manager = ShutdownManager { + shutdown_tx: Some(shutdown_tx), + shutdown_completion_rx: Some(shutdown_completion_rx), + }; + + tokio::spawn(node.start(shutdown_rx)); // create tauri app - tauri::Builder::default() + let app = tauri::Builder::default() // pass controller to the tauri state manager .manage(controller) .setup(|app| { @@ -104,13 +133,31 @@ async fn main() { Ok(()) }) - .on_menu_event(|event| menu::handle_menu_event(event)) + .on_menu_event(menu::handle_menu_event) .invoke_handler(tauri::generate_handler![ client_query_transport, client_command_transport, app_ready, ]) .menu(menu::get_menu()) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + .build(tauri::generate_context!()) + .expect("error while building tauri application"); + + app.run(move |app_handler, event| { + if let RunEvent::ExitRequested { .. } = event { + debug!("Closing all open windows..."); + app_handler + .windows() + .iter() + .for_each(|(window_name, window)| { + debug!("closing window: {window_name}"); + if let Err(e) = window.close() { + error!("failed to close window '{}': {:#?}", window_name, e); + } + }); + info!("Spacedrive shutting down..."); + shutdown_manager.shutdown(); + app_handler.exit(0); + } + }) } diff --git a/apps/desktop/src-tauri/src/menu.rs b/apps/desktop/src-tauri/src/menu.rs index 8e501161b..66e272f80 100644 --- a/apps/desktop/src-tauri/src/menu.rs +++ b/apps/desktop/src-tauri/src/menu.rs @@ -1,6 +1,8 @@ use std::env::consts; -use tauri::{AboutMetadata, CustomMenuItem, Menu, MenuItem, Submenu, WindowMenuEvent, Wry}; +use tauri::{ + AboutMetadata, CustomMenuItem, Manager, Menu, MenuItem, Submenu, WindowMenuEvent, Wry, +}; pub(crate) fn get_menu() -> Menu { match consts::OS { @@ -53,28 +55,25 @@ fn custom_menu_bar() -> Menu { CustomMenuItem::new("reload_app".to_string(), "Reload").accelerator("CmdOrCtrl+R"), ); - let view_menu = view_menu.add_item( + view_menu.add_item( CustomMenuItem::new("toggle_devtools".to_string(), "Toggle Developer Tools") .accelerator("CmdOrCtrl+Alt+I"), - ); - - view_menu + ) }; - let menu = Menu::new() + Menu::new() .add_submenu(Submenu::new("Spacedrive", app_menu)) .add_submenu(Submenu::new("File", file_menu)) .add_submenu(Submenu::new("Edit", edit_menu)) .add_submenu(Submenu::new("View", view_menu)) - .add_submenu(Submenu::new("Window", window_menu)); - - menu + .add_submenu(Submenu::new("Window", window_menu)) } pub(crate) fn handle_menu_event(event: WindowMenuEvent) { match event.menu_item_id() { "quit" => { - std::process::exit(0); + let app = event.window().app_handle(); + app.exit(0); } "close" => { let window = event.window(); @@ -82,7 +81,6 @@ pub(crate) fn handle_menu_event(event: WindowMenuEvent) { #[cfg(debug_assertions)] if window.is_devtools_open() { window.close_devtools(); - return; } else { window.close().unwrap(); } @@ -91,17 +89,17 @@ pub(crate) fn handle_menu_event(event: WindowMenuEvent) { window.close().unwrap(); } "reload_app" => { - event - .window() - .with_webview(|webview| { - #[cfg(target_os = "macos")] - { + #[cfg(target_os = "macos")] + { + event + .window() + .with_webview(|webview| { use crate::macos::reload_webview; reload_webview(webview.inner() as _); - } - }) - .unwrap(); + }) + .unwrap(); + } } #[cfg(debug_assertions)] "toggle_devtools" => { diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index 7f74760fb..1bdef0dd6 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -18,9 +18,9 @@ use actix_web::{ use actix_web_actors::ws; use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, oneshot}; -const DATA_DIR_ENV_VAR: &'static str = "DATA_DIR"; +const DATA_DIR_ENV_VAR: &str = "DATA_DIR"; #[derive(Serialize)] pub struct Event(CoreEvent); @@ -182,10 +182,8 @@ impl StreamHandler> for Socket { }; fut.into_actor(self).spawn(ctx); - - () }, - _ => (), + _ => {}, } } @@ -222,12 +220,12 @@ impl Handler for Socket { #[get("/")] async fn index() -> impl Responder { - format!("Spacedrive Server!") + "Spacedrive Server!" } #[get("/health")] async fn healthcheck() -> impl Responder { - format!("OK") + "OK" } #[get("/ws")] @@ -237,15 +235,14 @@ async fn ws_handler( controller: web::Data, server: web::Data>, ) -> Result { - let resp = ws::start( + ws::start( Socket { node_controller: controller, event_server: server, }, &req, stream, - ); - resp + ) } async fn not_found() -> impl Responder { @@ -284,14 +281,16 @@ async fn setup() -> (mpsc::Receiver, web::Data) { std::env::current_dir() .expect( - "Unable to get your currrent directory. Maybe try setting $DATA_DIR?", + "Unable to get your current directory. Maybe try setting $DATA_DIR?", ) .join("sdserver_data") }, }; - let (controller, event_receiver, node) = Node::new(data_dir_path).await; - tokio::spawn(node.start()); + let (controller, event_receiver, node, _shutdown_completion_rx) = + Node::new(data_dir_path).await; + let (_shutdown_tx, shutdown_rx) = oneshot::channel(); + tokio::spawn(node.start(shutdown_rx)); (event_receiver, web::Data::new(controller)) } diff --git a/core/Cargo.toml b/core/Cargo.toml index 6e6640a65..bc6968e0b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,19 +22,21 @@ futures = "0.3" data-encoding = "2.3.2" ring = "0.17.0-alpha.10" int-enum = "0.4.0" +rmp = "^0.8.11" +rmp-serde = "^1.1.0" # Project dependencies ts-rs = { version = "6.2", features = ["chrono-impl", "uuid-impl", "serde-compat"] } -prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.5.0" } +prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.5.2" } walkdir = "^2.3.2" uuid = { version = "^0.8.2", features = ["v4", "serde"]} sysinfo = "0.23.9" thiserror = "1.0.30" core-derive = { path = "./derive" } -tokio = { version = "1.17.0", features = ["sync", "rt"] } +tokio = { version = "^1.17.0", features = ["sync", "rt"] } include_dir = { version = "0.7.2", features = ["glob"] } -async-trait = "0.1.52" +async-trait = "^0.1.52" image = "0.24.1" webp = "0.2.2" ffmpeg-next = "5.0.3" diff --git a/core/bindings/JobReport.ts b/core/bindings/JobReport.ts index bd25c8d21..d6f964bee 100644 --- a/core/bindings/JobReport.ts +++ b/core/bindings/JobReport.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { JobStatus } from "./JobStatus"; -export interface JobReport { id: string, name: string, data: string | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: string, } \ No newline at end of file +export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: string, } \ No newline at end of file diff --git a/core/bindings/JobStatus.ts b/core/bindings/JobStatus.ts index 58c3a06b3..88aa16ae5 100644 --- a/core/bindings/JobStatus.ts +++ b/core/bindings/JobStatus.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed"; \ No newline at end of file +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"; \ No newline at end of file diff --git a/core/prisma/migrations/20220722181530_alter_uuids_to_bytes/migration.sql b/core/prisma/migrations/20220722181530_alter_uuids_to_bytes/migration.sql new file mode 100644 index 000000000..fb53d5d65 --- /dev/null +++ b/core/prisma/migrations/20220722181530_alter_uuids_to_bytes/migration.sql @@ -0,0 +1,145 @@ +/* + Warnings: + + - The primary key for the `jobs` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `data` on the `jobs` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `id` on the `jobs` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `pub_id` on the `nodes` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `pub_id` on the `tags` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `pub_id` on the `labels` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `pub_id` on the `spaces` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `pub_id` on the `locations` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `record_id` on the `sync_events` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `pub_id` on the `albums` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + - You are about to alter the column `pub_id` on the `comments` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_jobs" ( + "id" BLOB NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "node_id" INTEGER NOT NULL, + "action" INTEGER NOT NULL, + "status" INTEGER NOT NULL DEFAULT 0, + "data" BLOB, + "task_count" INTEGER NOT NULL DEFAULT 1, + "completed_task_count" INTEGER NOT NULL DEFAULT 0, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "seconds_elapsed" INTEGER NOT NULL DEFAULT 0, + CONSTRAINT "jobs_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_jobs" ("action", "completed_task_count", "data", "date_created", "date_modified", "id", "name", "node_id", "seconds_elapsed", "status", "task_count") SELECT "action", "completed_task_count", "data", "date_created", "date_modified", "id", "name", "node_id", "seconds_elapsed", "status", "task_count" FROM "jobs"; +DROP TABLE "jobs"; +ALTER TABLE "new_jobs" RENAME TO "jobs"; +CREATE TABLE "new_nodes" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "name" TEXT NOT NULL, + "platform" INTEGER NOT NULL DEFAULT 0, + "version" TEXT, + "last_seen" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "timezone" TEXT, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); +INSERT INTO "new_nodes" ("date_created", "id", "last_seen", "name", "platform", "pub_id", "timezone", "version") SELECT "date_created", "id", "last_seen", "name", "platform", "pub_id", "timezone", "version" FROM "nodes"; +DROP TABLE "nodes"; +ALTER TABLE "new_nodes" RENAME TO "nodes"; +CREATE UNIQUE INDEX "nodes_pub_id_key" ON "nodes"("pub_id"); +CREATE TABLE "new_tags" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "name" TEXT, + "color" TEXT, + "total_files" INTEGER DEFAULT 0, + "redundancy_goal" INTEGER DEFAULT 1, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); +INSERT INTO "new_tags" ("color", "date_created", "date_modified", "id", "name", "pub_id", "redundancy_goal", "total_files") SELECT "color", "date_created", "date_modified", "id", "name", "pub_id", "redundancy_goal", "total_files" FROM "tags"; +DROP TABLE "tags"; +ALTER TABLE "new_tags" RENAME TO "tags"; +CREATE UNIQUE INDEX "tags_pub_id_key" ON "tags"("pub_id"); +CREATE TABLE "new_labels" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "name" TEXT, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); +INSERT INTO "new_labels" ("date_created", "date_modified", "id", "name", "pub_id") SELECT "date_created", "date_modified", "id", "name", "pub_id" FROM "labels"; +DROP TABLE "labels"; +ALTER TABLE "new_labels" RENAME TO "labels"; +CREATE UNIQUE INDEX "labels_pub_id_key" ON "labels"("pub_id"); +CREATE TABLE "new_spaces" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "name" TEXT, + "description" TEXT, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); +INSERT INTO "new_spaces" ("date_created", "date_modified", "description", "id", "name", "pub_id") SELECT "date_created", "date_modified", "description", "id", "name", "pub_id" FROM "spaces"; +DROP TABLE "spaces"; +ALTER TABLE "new_spaces" RENAME TO "spaces"; +CREATE UNIQUE INDEX "spaces_pub_id_key" ON "spaces"("pub_id"); +CREATE TABLE "new_locations" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "node_id" INTEGER, + "name" TEXT, + "local_path" TEXT, + "total_capacity" INTEGER, + "available_capacity" INTEGER, + "filesystem" TEXT, + "disk_type" INTEGER, + "is_removable" BOOLEAN, + "is_online" BOOLEAN NOT NULL DEFAULT true, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "locations_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_locations" ("available_capacity", "date_created", "disk_type", "filesystem", "id", "is_online", "is_removable", "local_path", "name", "node_id", "pub_id", "total_capacity") SELECT "available_capacity", "date_created", "disk_type", "filesystem", "id", "is_online", "is_removable", "local_path", "name", "node_id", "pub_id", "total_capacity" FROM "locations"; +DROP TABLE "locations"; +ALTER TABLE "new_locations" RENAME TO "locations"; +CREATE UNIQUE INDEX "locations_pub_id_key" ON "locations"("pub_id"); +CREATE TABLE "new_sync_events" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "node_id" INTEGER NOT NULL, + "timestamp" TEXT NOT NULL, + "record_id" BLOB NOT NULL, + "kind" INTEGER NOT NULL, + "column" TEXT, + "value" TEXT NOT NULL, + CONSTRAINT "sync_events_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_sync_events" ("column", "id", "kind", "node_id", "record_id", "timestamp", "value") SELECT "column", "id", "kind", "node_id", "record_id", "timestamp", "value" FROM "sync_events"; +DROP TABLE "sync_events"; +ALTER TABLE "new_sync_events" RENAME TO "sync_events"; +CREATE TABLE "new_albums" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "name" TEXT NOT NULL, + "is_hidden" BOOLEAN NOT NULL DEFAULT false, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); +INSERT INTO "new_albums" ("date_created", "date_modified", "id", "is_hidden", "name", "pub_id") SELECT "date_created", "date_modified", "id", "is_hidden", "name", "pub_id" FROM "albums"; +DROP TABLE "albums"; +ALTER TABLE "new_albums" RENAME TO "albums"; +CREATE UNIQUE INDEX "albums_pub_id_key" ON "albums"("pub_id"); +CREATE TABLE "new_comments" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "content" TEXT NOT NULL, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "file_id" INTEGER, + CONSTRAINT "comments_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_comments" ("content", "date_created", "date_modified", "file_id", "id", "pub_id") SELECT "content", "date_created", "date_modified", "file_id", "id", "pub_id" FROM "comments"; +DROP TABLE "comments"; +ALTER TABLE "new_comments" RENAME TO "comments"; +CREATE UNIQUE INDEX "comments_pub_id_key" ON "comments"("pub_id"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index 47a0da0c1..5808ff3e6 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -23,7 +23,7 @@ model SyncEvent { node_id Int timestamp String // individual record pub id OR compound many-to-many pub ids - record_id String + record_id Bytes // the type of operation, I.E: CREATE, UPDATE, DELETE as an enum kind Int // the column name for atomic update operations @@ -51,7 +51,7 @@ model Statistics { model Node { id Int @id @default(autoincrement()) - pub_id String @unique + pub_id Bytes @unique name String platform Int @default(0) version String? @@ -67,7 +67,7 @@ model Node { } model Volume { - id Int @id() @default(autoincrement()) + id Int @id @default(autoincrement()) node_id Int name String mount_point String @@ -84,7 +84,7 @@ model Volume { model Location { id Int @id @default(autoincrement()) - pub_id String @unique + pub_id Bytes @unique node_id Int? name String? local_path String? @@ -226,7 +226,7 @@ model MediaData { model Tag { id Int @id @default(autoincrement()) - pub_id String @unique + pub_id Bytes @unique name String? color String? total_files Int? @default(0) @@ -253,7 +253,7 @@ model TagOnFile { model Label { id Int @id @default(autoincrement()) - pub_id String @unique + pub_id Bytes @unique name String? date_created DateTime @default(now()) date_modified DateTime @default(now()) @@ -277,7 +277,7 @@ model LabelOnFile { model Space { id Int @id @default(autoincrement()) - pub_id String @unique + pub_id Bytes @unique name String? description String? date_created DateTime @default(now()) @@ -301,12 +301,12 @@ model FileInSpace { } model Job { - id String @id + id Bytes @id name String node_id Int action Int status Int @default(0) - data String? + data Bytes? task_count Int @default(1) completed_task_count Int @default(0) @@ -320,7 +320,7 @@ model Job { model Album { id Int @id @default(autoincrement()) - pub_id String @unique + pub_id Bytes @unique name String is_hidden Boolean @default(false) @@ -347,7 +347,7 @@ model FileInAlbum { model Comment { id Int @id @default(autoincrement()) - pub_id String @unique + pub_id Bytes @unique content String date_created DateTime @default(now()) date_modified DateTime @default(now()) diff --git a/core/src/encode/thumb.rs b/core/src/encode/thumb.rs index 065ca20d5..bde57e343 100644 --- a/core/src/encode/thumb.rs +++ b/core/src/encode/thumb.rs @@ -1,46 +1,67 @@ use crate::{ - job::{Job, JobReportUpdate, JobResult, WorkerContext}, + job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, library::LibraryContext, prisma::file_path, sys, CoreEvent, }; use image::{self, imageops, DynamicImage, GenericImageView}; -use log::{debug, error, info}; -use std::error::Error; -use std::ops::Deref; -use std::path::{Path, PathBuf}; -use tokio::fs; +use log::{error, info, trace}; +use serde::{Deserialize, Serialize}; +use std::{ + error::Error, + ops::Deref, + path::{Path, PathBuf}, +}; +use tokio::{fs, task::block_in_place}; use webp::Encoder; -#[derive(Debug, Clone)] -pub struct ThumbnailJob { +static THUMBNAIL_SIZE_FACTOR: f32 = 0.2; +static THUMBNAIL_QUALITY: f32 = 30.0; +pub static THUMBNAIL_CACHE_DIR_NAME: &str = "thumbnails"; +pub const THUMBNAIL_JOB_NAME: &str = "thumbnailer"; + +pub struct ThumbnailJob {} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ThumbnailJobInit { pub location_id: i32, pub path: PathBuf, pub background: bool, } -static THUMBNAIL_SIZE_FACTOR: f32 = 0.2; -static THUMBNAIL_QUALITY: f32 = 30.0; -pub static THUMBNAIL_CACHE_DIR_NAME: &str = "thumbnails"; +#[derive(Debug, Serialize, Deserialize)] +pub struct ThumbnailJobState { + thumbnail_dir: PathBuf, + root_path: PathBuf, +} #[async_trait::async_trait] -impl Job for ThumbnailJob { +impl StatefulJob for ThumbnailJob { + type Init = ThumbnailJobInit; + type Data = ThumbnailJobState; + type Step = file_path::Data; + fn name(&self) -> &'static str { - "thumbnailer" + THUMBNAIL_JOB_NAME } - async fn run(&self, ctx: WorkerContext) -> JobResult { + + async fn init( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult { let library_ctx = ctx.library_ctx(); let thumbnail_dir = library_ctx .config() .data_directory() .join(THUMBNAIL_CACHE_DIR_NAME) - .join(self.location_id.to_string()); + .join(state.init.location_id.to_string()); - let location = sys::get_location(&library_ctx, self.location_id).await?; + let location = sys::get_location(&library_ctx, state.init.location_id).await?; info!( "Searching for images in location {} at path {:#?}", - location.id, self.path + location.id, state.init.path ); // create all necessary directories if they don't exist @@ -48,7 +69,8 @@ impl Job for ThumbnailJob { let root_path = location.path.unwrap(); // query database for all files in this location that need thumbnails - let image_files = get_images(&library_ctx, self.location_id, &self.path).await?; + let image_files = + get_images(&library_ctx, state.init.location_id, &state.init.path).await?; info!("Found {:?} files", image_files.len()); ctx.progress(vec![ @@ -56,59 +78,95 @@ impl Job for ThumbnailJob { JobReportUpdate::Message(format!("Preparing to process {} files", image_files.len())), ]); - for (i, image_file) in image_files.iter().enumerate() { - ctx.progress(vec![JobReportUpdate::Message(format!( - "Processing {}", - image_file.materialized_path - ))]); + state.data = Some(ThumbnailJobState { + thumbnail_dir, + root_path, + }); + state.steps = image_files.into_iter().collect(); - // assemble the file path - let path = Path::new(&root_path).join(&image_file.materialized_path); - debug!("image_file {:?}", image_file); + Ok(()) + } - // get cas_id, if none found skip - let cas_id = match image_file.file() { - Ok(file) => { - if let Some(f) = file { - f.cas_id.clone() - } else { - info!( - "skipping thumbnail generation for {}", - image_file.materialized_path - ); - continue; - } + async fn execute_step( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult { + let step = &state.steps[0]; + ctx.progress(vec![JobReportUpdate::Message(format!( + "Processing {}", + step.materialized_path + ))]); + + let data = state + .data + .as_ref() + .expect("critical error: missing data on job state"); + + // assemble the file path + let path = data.root_path.join(&step.materialized_path); + trace!("image_file {:?}", step); + + // get cas_id, if none found skip + let cas_id = match step.file() { + Ok(file) => { + if let Some(f) = file { + f.cas_id.clone() + } else { + info!( + "skipping thumbnail generation for {}", + step.materialized_path + ); + return Ok(()); } - Err(_) => { - error!("Error getting cas_id {:?}", image_file.materialized_path); - continue; - } - }; - - // Define and write the WebP-encoded file to a given path - let output_path = thumbnail_dir.join(&cas_id).with_extension("webp"); - - // check if file exists at output path - if !output_path.exists() { - info!("Writing {:?} to {:?}", path, output_path); - tokio::spawn(async move { - if let Err(e) = generate_thumbnail(&path, &output_path).await { - error!("Error generating thumb {:?}", e); - } - }); - - ctx.progress(vec![JobReportUpdate::CompletedTaskCount(i + 1)]); - - if !self.background { - ctx.library_ctx() - .emit(CoreEvent::NewThumbnail { cas_id }) - .await; - }; - } else { - info!("Thumb exists, skipping... {}", output_path.display()); } + Err(_) => { + error!("Error getting cas_id {:?}", step.materialized_path); + return Ok(()); + } + }; + + // Define and write the WebP-encoded file to a given path + let output_path = data.thumbnail_dir.join(&cas_id).with_extension("webp"); + + // check if file exists at output path + if !output_path.exists() { + info!("Writing {:?} to {:?}", path, output_path); + + if let Err(e) = generate_thumbnail(&path, &output_path).await { + error!("Error generating thumb {:?}", e); + } + + if !state.init.background { + ctx.library_ctx() + .emit(CoreEvent::NewThumbnail { cas_id }) + .await; + }; + } else { + info!("Thumb exists, skipping... {}", output_path.display()); } + ctx.progress(vec![JobReportUpdate::CompletedTaskCount( + state.step_number + 1, + )]); + + Ok(()) + } + + async fn finalize( + &self, + _ctx: WorkerContext, + state: &mut JobState, + ) -> Result<(), JobError> { + let data = state + .data + .as_ref() + .expect("critical error: missing data on job state"); + info!( + "Finished thumbnail generation for location {} at {}", + state.init.location_id, + data.root_path.display() + ); Ok(()) } } @@ -117,25 +175,29 @@ pub async fn generate_thumbnail>( file_path: P, output_path: P, ) -> Result<(), Box> { - // Using `image` crate, open the included .jpg file - let img = image::open(file_path)?; - let (w, h) = img.dimensions(); - // Optionally, resize the existing photo and convert back into DynamicImage - let img = DynamicImage::ImageRgba8(imageops::resize( - &img, - (w as f32 * THUMBNAIL_SIZE_FACTOR) as u32, - (h as f32 * THUMBNAIL_SIZE_FACTOR) as u32, - imageops::FilterType::Triangle, - )); - // Create the WebP encoder for the above image - let encoder = Encoder::from_image(&img)?; + // Webp creation has blocking code + let webp = block_in_place(|| -> Result, Box> { + // Using `image` crate, open the included .jpg file + let img = image::open(file_path)?; + let (w, h) = img.dimensions(); + // Optionally, resize the existing photo and convert back into DynamicImage + let img = DynamicImage::ImageRgba8(imageops::resize( + &img, + (w as f32 * THUMBNAIL_SIZE_FACTOR) as u32, + (h as f32 * THUMBNAIL_SIZE_FACTOR) as u32, + imageops::FilterType::Triangle, + )); + // Create the WebP encoder for the above image + let encoder = Encoder::from_image(&img)?; - // Encode the image at a specified quality 0-100 + // Encode the image at a specified quality 0-100 + + // Type WebPMemory is !Send, which makes the Future in this function !Send, + // this make us `deref` to have a `&[u8]` and then `to_owned` to make a Vec + // which implies on a unwanted clone... + Ok(encoder.encode(THUMBNAIL_QUALITY).deref().to_owned()) + })?; - // Type WebPMemory is !Send, which makes the Future in this function !Send, - // this make us `deref` to have a `&[u8]` and then `to_owned` to make a Vec - // which implies on a unwanted clone... - let webp = encoder.encode(THUMBNAIL_QUALITY).deref().to_owned(); fs::write(output_path, &webp).await?; Ok(()) diff --git a/core/src/file/cas/identifier.rs b/core/src/file/cas/identifier.rs index a05684eda..a2f329365 100644 --- a/core/src/file/cas/identifier.rs +++ b/core/src/file/cas/identifier.rs @@ -1,44 +1,71 @@ use super::checksum::generate_cas_id; + use crate::{ file::FileError, - job::JobReportUpdate, - job::{Job, JobResult, WorkerContext}, + job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, library::LibraryContext, prisma::{file, file_path}, sys::get_location, + sys::LocationResource, }; use chrono::{DateTime, FixedOffset}; use futures::future::join_all; use log::{error, info}; use prisma_client_rust::{prisma_models::PrismaValue, raw, raw::Raw, Direction}; use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; -use std::path::{Path, PathBuf}; +use std::{ + collections::{HashMap, HashSet}, + path::{Path, PathBuf}, +}; use tokio::{fs, io}; -// FileIdentifierJob takes file_paths without a file_id and uniquely identifies them +// we break this job into chunks of 100 to improve performance +static CHUNK_SIZE: usize = 100; +pub const IDENTIFIER_JOB_NAME: &str = "file_identifier"; + +pub struct FileIdentifierJob {} + +// FileIdentifierJobInit takes file_paths without a file_id and uniquely identifies them // first: generating the cas_id and extracting metadata // finally: creating unique file records, and linking them to their file_paths -#[derive(Debug)] -pub struct FileIdentifierJob { +#[derive(Serialize, Deserialize, Clone)] +pub struct FileIdentifierJobInit { pub location_id: i32, pub path: PathBuf, } -// we break this job into chunks of 100 to improve performance -static CHUNK_SIZE: usize = 100; +#[derive(Serialize, Deserialize)] +pub struct FileIdentifierJobState { + total_count: usize, + task_count: usize, + location: LocationResource, + location_path: PathBuf, + cursor: i32, +} #[async_trait::async_trait] -impl Job for FileIdentifierJob { +impl StatefulJob for FileIdentifierJob { + type Init = FileIdentifierJobInit; + type Data = FileIdentifierJobState; + type Step = (); + fn name(&self) -> &'static str { - "file_identifier" + IDENTIFIER_JOB_NAME } - async fn run(&self, ctx: WorkerContext) -> JobResult { + async fn init( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult { info!("Identifying orphan file paths..."); - let location = get_location(&ctx.library_ctx(), self.location_id).await?; - let location_path = location.path.unwrap_or_else(|| "".to_string()); + let location = get_location(&ctx.library_ctx(), state.init.location_id).await?; + let location_path = if let Some(ref path) = location.path { + path.clone() + } else { + PathBuf::new() + }; let total_count = count_orphan_file_paths(&ctx.library_ctx(), location.id.into()).await?; info!("Found {} orphan file paths", total_count); @@ -49,159 +76,188 @@ impl Job for FileIdentifierJob { // update job with total task count based on orphan file_paths count ctx.progress(vec![JobReportUpdate::TaskCount(task_count)]); - let mut completed: usize = 0; - let mut cursor: i32 = 1; - // loop until task count is complete - while completed < task_count { - // link file_path ids to a CreateFile struct containing unique file data - let mut chunk: HashMap = HashMap::new(); - let mut cas_lookup: HashMap = HashMap::new(); + state.data = Some(FileIdentifierJobState { + total_count, + task_count, + location, + location_path, + cursor: 1, + }); - // get chunk of orphans to process - let file_paths = match get_orphan_file_paths(&ctx.library_ctx(), cursor).await { - Ok(file_paths) => file_paths, + state.steps = (0..task_count).map(|_| ()).collect(); + + Ok(()) + } + + async fn execute_step( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult { + // link file_path ids to a CreateFile struct containing unique file data + let mut chunk: HashMap = HashMap::new(); + let mut cas_lookup: HashMap = HashMap::new(); + + let data = state + .data + .as_mut() + .expect("critical error: missing data on job state"); + + // get chunk of orphans to process + let file_paths = match get_orphan_file_paths(&ctx.library_ctx(), data.cursor).await { + Ok(file_paths) => file_paths, + Err(e) => { + info!("Error getting orphan file paths: {:#?}", e); + return Ok(()); + } + }; + info!( + "Processing {:?} orphan files. ({} completed of {})", + file_paths.len(), + state.step_number, + data.task_count + ); + + // analyze each file_path + for file_path in &file_paths { + // get the cas_id and extract metadata + match prepare_file(&data.location_path, file_path).await { + Ok(file) => { + let cas_id = file.cas_id.clone(); + // create entry into chunks for created file data + chunk.insert(file_path.id, file); + cas_lookup.insert(cas_id, file_path.id); + } Err(e) => { - info!("Error getting orphan file paths: {:#?}", e); + info!("Error processing file: {:#?}", e); continue; } }; - info!( - "Processing {:?} orphan files. ({} completed of {})", - file_paths.len(), - completed, - task_count - ); + } - // analyze each file_path - for file_path in &file_paths { - // get the cas_id and extract metadata - match prepare_file(&location_path, file_path).await { - Ok(file) => { - let cas_id = file.cas_id.clone(); - // create entry into chunks for created file data - chunk.insert(file_path.id, file); - cas_lookup.insert(cas_id, file_path.id); - } - Err(e) => { - info!("Error processing file: {:#?}", e); - continue; - } - }; - } + // find all existing files by cas id + let generated_cas_ids = chunk.values().map(|c| c.cas_id.clone()).collect(); + let existing_files = ctx + .library_ctx() + .db + .file() + .find_many(vec![file::cas_id::in_vec(generated_cas_ids)]) + .exec() + .await?; - // find all existing files by cas id - let generated_cas_ids = chunk.values().map(|c| c.cas_id.clone()).collect(); - let existing_files = ctx - .library_ctx() - .db - .file() - .find_many(vec![file::cas_id::in_vec(generated_cas_ids)]) - .exec() - .await?; + info!("Found {} existing files", existing_files.len()); - info!("Found {} existing files", existing_files.len()); + // link those existing files to their file paths + // Had to put the file_path in a variable outside of the closure, to satisfy the borrow checker + let library_ctx = ctx.library_ctx(); + let prisma_file_path = library_ctx.db.file_path(); - // link those existing files to their file paths - // Had to put the file_path in a variable outside of the closure, to satisfy the borrow checker - let library_ctx = ctx.library_ctx(); - let prisma_file_path = library_ctx.db.file_path(); - for result in join_all(existing_files.iter().map(|file| { - prisma_file_path - .find_unique(file_path::id::equals( - *cas_lookup.get(&file.cas_id).unwrap(), - )) - .update(vec![file_path::file_id::set(Some(file.id))]) - .exec() - })) + for existing_file in &existing_files { + if let Err(e) = update_file_id_by_cas_id( + &prisma_file_path, + &cas_lookup, + &existing_file.cas_id, + existing_file.id, + ) .await { - if let Err(e) = result { - error!("Error linking file: {:#?}", e); - } + info!("Error updating file_id: {:#?}", e); } + } - let existing_files_cas_ids = existing_files - .iter() - .map(|file| file.cas_id.clone()) - .collect::>(); + let existing_files_cas_ids = existing_files + .iter() + .map(|file| file.cas_id.clone()) + .collect::>(); - // extract files that don't already exist in the database - let new_files = chunk - .iter() - .map(|(_id, create_file)| create_file) - .filter(|create_file| !existing_files_cas_ids.contains(&create_file.cas_id)) - .collect::>(); + // extract files that don't already exist in the database + let new_files = chunk + .iter() + .map(|(_id, create_file)| create_file) + .filter(|create_file| !existing_files_cas_ids.contains(&create_file.cas_id)) + .collect::>(); - // assemble prisma values for new unique files - let mut values = Vec::with_capacity(new_files.len() * 3); - for file in &new_files { - values.extend([ - PrismaValue::String(file.cas_id.clone()), - PrismaValue::Int(file.size_in_bytes), - PrismaValue::DateTime(file.date_created), - ]); - } - - // create new file records with assembled values - let created_files: Vec = ctx - .library_ctx() - .db - ._query_raw(Raw::new( - &format!( - "INSERT INTO files (cas_id, size_in_bytes, date_created) VALUES {} - ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id", - vec!["({}, {}, {})"; new_files.len()].join(",") - ), - values, - )) - .await - .unwrap_or_else(|e| { - error!("Error inserting files: {:#?}", e); - Vec::new() - }); - - // This code is duplicates, is this right? - for result in join_all(created_files.iter().map(|file| { - // associate newly created files with their respective file_paths - // TODO: this is potentially bottle necking the chunk system, individually linking file_path to file, 100 queries per chunk - // - insert many could work, but I couldn't find a good way to do this in a single SQL query - prisma_file_path - .find_unique(file_path::id::equals( - *cas_lookup.get(&file.cas_id).unwrap(), - )) - .update(vec![file_path::file_id::set(Some(file.id))]) - .exec() - })) - .await - { - if let Err(e) = result { - error!("Error linking file: {:#?}", e); - } - } - - // handle loop end - let last_row = match file_paths.last() { - Some(l) => l, - None => { - break; - } - }; - cursor = last_row.id; - completed += 1; - - ctx.progress(vec![ - JobReportUpdate::CompletedTaskCount(completed), - JobReportUpdate::Message(format!( - "Processed {} of {} orphan files", - completed * CHUNK_SIZE, - total_count - )), + // assemble prisma values for new unique files + let mut values = Vec::with_capacity(new_files.len() * 3); + for file in &new_files { + values.extend([ + PrismaValue::String(file.cas_id.clone()), + PrismaValue::Int(file.size_in_bytes), + PrismaValue::DateTime(file.date_created), ]); } + // create new file records with assembled values + let created_files: Vec = ctx + .library_ctx() + .db + ._query_raw(Raw::new( + &format!( + "INSERT INTO files (cas_id, size_in_bytes, date_created) VALUES {} + ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id", + vec!["({}, {}, {})"; new_files.len()].join(",") + ), + values, + )) + .await + .unwrap_or_else(|e| { + error!("Error inserting files: {:#?}", e); + Vec::new() + }); + + for created_file in created_files { + // associate newly created files with their respective file_paths + // TODO: this is potentially bottle necking the chunk system, individually linking file_path to file, 100 queries per chunk + // - insert many could work, but I couldn't find a good way to do this in a single SQL query + if let Err(e) = update_file_id_by_cas_id( + &prisma_file_path, + &cas_lookup, + &created_file.cas_id, + created_file.id, + ) + .await + { + info!("Error updating file_id: {:#?}", e); + } + } + + // handle last step + if let Some(last_row) = file_paths.last() { + data.cursor = last_row.id; + } else { + return Ok(()); + } + + ctx.progress(vec![ + JobReportUpdate::CompletedTaskCount(state.step_number), + JobReportUpdate::Message(format!( + "Processed {} of {} orphan files", + state.step_number * CHUNK_SIZE, + data.total_count + )), + ]); + // let _remaining = count_orphan_file_paths(&ctx.core_ctx, location.id.into()).await?; Ok(()) } + + async fn finalize( + &self, + _ctx: WorkerContext, + state: &mut JobState, + ) -> Result<(), JobError> { + let data = state + .data + .as_ref() + .expect("critical error: missing data on job state"); + info!( + "Finalizing identifier job at {}, total of {} tasks", + data.location_path.display(), + data.task_count + ); + + Ok(()) + } } #[derive(Deserialize, Serialize, Debug)] @@ -241,7 +297,7 @@ pub async fn get_orphan_file_paths( .take(CHUNK_SIZE as i64) .exec() .await - .map_err(|e| e.into()) + .map_err(Into::into) } #[derive(Deserialize, Serialize, Debug)] @@ -287,3 +343,16 @@ pub async fn prepare_file( date_created: file_path.date_created, }) } + +async fn update_file_id_by_cas_id( + prisma_file_path: &file_path::Actions<'_>, + cas_lookup: &HashMap, + file_cas_id: &str, + file_id: i32, +) -> prisma_client_rust::Result> { + prisma_file_path + .find_unique(file_path::id::equals(*cas_lookup.get(file_cas_id).unwrap())) + .update(vec![file_path::file_id::set(Some(file_id))]) + .exec() + .await +} diff --git a/core/src/file/indexer/mod.rs b/core/src/file/indexer/mod.rs index 942a1e3b8..46969ad94 100644 --- a/core/src/file/indexer/mod.rs +++ b/core/src/file/indexer/mod.rs @@ -1,37 +1,270 @@ -use crate::job::{Job, JobReportUpdate, JobResult, WorkerContext}; -use std::path::PathBuf; +use crate::{ + job::{JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, + sys::{create_location, LocationResource}, +}; +use chrono::{DateTime, Utc}; +use log::{error, info}; +use prisma_client_rust::{raw, raw::Raw, PrismaValue}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + ffi::OsStr, + path::{Path, PathBuf}, + time::Duration, +}; +use tokio::{fs, time::Instant}; +use walkdir::{DirEntry, WalkDir}; -use self::scan::ScanProgress; -mod scan; +static BATCH_SIZE: usize = 100; +pub const INDEXER_JOB_NAME: &str = "indexer"; -// Re-exporting -pub use scan::*; +#[derive(Clone)] +pub enum ScanProgress { + ChunkCount(usize), + SavedChunks(usize), + Message(String), +} -use scan::scan_path; +pub struct IndexerJob {} -#[derive(Debug)] -pub struct IndexerJob { +#[derive(Serialize, Deserialize, Clone)] +pub struct IndexerJobInit { pub path: PathBuf, } -#[async_trait::async_trait] -impl Job for IndexerJob { - fn name(&self) -> &'static str { - "indexer" +#[derive(Serialize, Deserialize)] +pub struct IndexerJobData { + location: LocationResource, + db_write_start: DateTime, + scan_read_time: Duration, + total_paths: usize, +} + +pub(crate) type IndexerJobStep = Vec<(PathBuf, i32, Option, bool)>; + +impl IndexerJobData { + fn on_scan_progress(ctx: WorkerContext, progress: Vec) { + ctx.progress( + progress + .iter() + .map(|p| match p.clone() { + ScanProgress::ChunkCount(c) => JobReportUpdate::TaskCount(c), + ScanProgress::SavedChunks(p) => JobReportUpdate::CompletedTaskCount(p), + ScanProgress::Message(m) => JobReportUpdate::Message(m), + }) + .collect(), + ) } - async fn run(&self, ctx: WorkerContext) -> JobResult { - scan_path(&ctx.library_ctx(), &self.path, move |p| { - ctx.progress( - p.iter() - .map(|p| match p.clone() { - ScanProgress::ChunkCount(c) => JobReportUpdate::TaskCount(c), - ScanProgress::SavedChunks(p) => JobReportUpdate::CompletedTaskCount(p), - ScanProgress::Message(m) => JobReportUpdate::Message(m), - }) - .collect(), - ) +} + +#[async_trait::async_trait] +impl StatefulJob for IndexerJob { + type Init = IndexerJobInit; + type Data = IndexerJobData; + type Step = IndexerJobStep; + + fn name(&self) -> &'static str { + INDEXER_JOB_NAME + } + + // creates a vector of valid path buffers from a directory + async fn init( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult { + let location = create_location(&ctx.library_ctx(), &state.init.path).await?; + + // query db to highers id, so we can increment it for the new files indexed + #[derive(Deserialize, Serialize, Debug)] + struct QueryRes { + id: Option, + } + // grab the next id so we can increment in memory for batch inserting + let first_file_id = match ctx + .library_ctx() + .db + ._query_raw::(raw!("SELECT MAX(id) id FROM file_paths")) + .await + { + Ok(rows) => rows[0].id.unwrap_or(0), + Err(e) => panic!("Error querying for next file id: {:#?}", e), + }; + + //check is path is a directory + if !state.init.path.is_dir() { + // return Err(anyhow::anyhow!("{} is not a directory", &path)); + panic!("{:#?} is not a directory", state.init.path); + } + + // spawn a dedicated thread to scan the directory for performance + let path = state.init.path.clone(); + let inner_ctx = ctx.clone(); + let (paths, scan_start) = tokio::task::spawn_blocking(move || { + // store every valid path discovered + let mut paths: Vec<(PathBuf, i32, Option, bool)> = Vec::new(); + // store a hashmap of directories to their file ids for fast lookup + let mut dirs = HashMap::new(); + // begin timer for logging purposes + let scan_start = Instant::now(); + + let mut next_file_id = first_file_id; + let mut get_id = || { + next_file_id += 1; + next_file_id + }; + // walk through directory recursively + for entry in WalkDir::new(&path).into_iter().filter_entry(|dir| { + // check if entry is approved + !is_hidden(dir) && !is_app_bundle(dir) && !is_node_modules(dir) && !is_library(dir) + }) { + // extract directory entry or log and continue if failed + let entry = match entry { + Ok(entry) => entry, + Err(e) => { + error!("Error reading file {}", e); + continue; + } + }; + let path = entry.path(); + + info!("Found filesystem path: {:?}", path); + + let parent_path = path + .parent() + .unwrap_or_else(|| Path::new("")) + .to_str() + .unwrap_or(""); + let parent_dir_id = dirs.get(&*parent_path); + + let path_str = match path.as_os_str().to_str() { + Some(path_str) => path_str, + None => { + error!("Error reading file {}", &path.display()); + continue; + } + }; + + IndexerJobData::on_scan_progress( + inner_ctx.clone(), + vec![ + ScanProgress::Message(format!("Scanning {}", path_str)), + ScanProgress::ChunkCount(paths.len() / BATCH_SIZE), + ], + ); + + let file_id = get_id(); + let file_type = entry.file_type(); + let is_dir = file_type.is_dir(); + + if is_dir || file_type.is_file() { + paths.push((path.to_owned(), file_id, parent_dir_id.cloned(), is_dir)); + } + + if is_dir { + let _path = match path.to_str() { + Some(path) => path.to_owned(), + None => continue, + }; + dirs.insert(_path, file_id); + } + } + (paths, scan_start) }) - .await + .await?; + + state.data = Some(IndexerJobData { + location, + db_write_start: Utc::now(), + scan_read_time: scan_start.elapsed(), + total_paths: paths.len(), + }); + + state.steps = paths + .chunks(BATCH_SIZE) + .enumerate() + .map(|(i, chunk)| { + IndexerJobData::on_scan_progress( + ctx.clone(), + vec![ + ScanProgress::SavedChunks(i as usize), + ScanProgress::Message(format!( + "Writing {} of {} to db", + i * chunk.len(), + paths.len(), + )), + ], + ); + chunk.to_vec() + }) + .collect(); + + Ok(()) + } + + async fn execute_step( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult { + // vector to store active models + let mut files = Vec::new(); + let step = &state.steps[0]; + + let data = state + .data + .as_ref() + .expect("critical error: missing data on job state"); + + for (file_path, file_id, parent_dir_id, is_dir) in step { + files.extend( + match prepare_values(file_path, *file_id, &data.location, parent_dir_id, *is_dir) + .await + { + Ok(values) => values.to_vec(), + Err(e) => { + error!("Error creating file model from path {:?}: {}", file_path, e); + continue; + } + }, + ); + } + + let raw = Raw::new( + &format!(" + INSERT INTO file_paths (id, is_dir, location_id, materialized_path, name, extension, parent_id, date_created) + VALUES {} + ", + vec!["({}, {}, {}, {}, {}, {}, {}, {})"; step.len()].join(", ") + ), + files + ); + + let count = ctx.library_ctx().db._execute_raw(raw).await; + + info!("Inserted {:?} records", count); + + Ok(()) + } + + async fn finalize( + &self, + _ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult { + let data = state + .data + .as_ref() + .expect("critical error: missing data on job state"); + info!( + "scan of {:?} completed in {:?}. {:?} files found. db write completed in {:?}", + state.init.path, + data.scan_read_time, + data.total_paths, + Utc::now() - data.db_write_start, + ); + + Ok(()) } } @@ -48,3 +281,104 @@ impl Job for IndexerJob { // // sub-paths that are ignored // pub always_ignored_sub_paths: Option, // } + +// reads a file at a path and creates an ActiveModel with metadata +async fn prepare_values( + file_path: impl AsRef, + id: i32, + location: &LocationResource, + parent_id: &Option, + is_dir: bool, +) -> Result<[PrismaValue; 8], std::io::Error> { + let file_path = file_path.as_ref(); + + let metadata = fs::metadata(file_path).await?; + let location_path = location.path.as_ref().unwrap(); + // let size = metadata.len(); + let name; + let extension; + let date_created: DateTime = metadata.created().unwrap().into(); + + // if the 'file_path' is not a directory, then get the extension and name. + + // if 'file_path' is a directory, set extension to an empty string to avoid periods in folder names + // - being interpreted as file extensions + if is_dir { + extension = "".to_string(); + name = extract_name(file_path.file_name()); + } else { + extension = extract_name(file_path.extension()); + name = extract_name(file_path.file_stem()); + } + + let materialized_path = file_path.strip_prefix(location_path).unwrap(); + let materialized_path_as_string = materialized_path.to_str().unwrap_or("").to_owned(); + + let values = [ + PrismaValue::Int(id as i64), + PrismaValue::Boolean(metadata.is_dir()), + PrismaValue::Int(location.id as i64), + PrismaValue::String(materialized_path_as_string), + PrismaValue::String(name), + PrismaValue::String(extension.to_lowercase()), + parent_id + .map(|id| PrismaValue::Int(id as i64)) + .unwrap_or(PrismaValue::Null), + PrismaValue::DateTime(date_created.into()), + ]; + + Ok(values) +} + +// extract name from OsStr returned by PathBuff +fn extract_name(os_string: Option<&OsStr>) -> String { + os_string + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .to_owned() +} + +fn is_hidden(entry: &DirEntry) -> bool { + entry + .file_name() + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false) +} + +fn is_library(entry: &DirEntry) -> bool { + entry + .path() + .to_str() + // make better this is shit + .map(|s| s.contains("/Library/")) + .unwrap_or(false) +} + +fn is_node_modules(entry: &DirEntry) -> bool { + entry + .file_name() + .to_str() + .map(|s| s.contains("node_modules")) + .unwrap_or(false) +} + +fn is_app_bundle(entry: &DirEntry) -> bool { + let is_dir = entry.metadata().unwrap().is_dir(); + let contains_dot = entry + .file_name() + .to_str() + .map(|s| s.contains(".app") | s.contains(".bundle")) + .unwrap_or(false); + + // let is_app_bundle = is_dir && contains_dot; + // if is_app_bundle { + // let path_buff = entry.path(); + // let path = path_buff.to_str().unwrap(); + + // self::path(&path, ); + // } + + is_dir && contains_dot +} diff --git a/core/src/file/indexer/scan.rs b/core/src/file/indexer/scan.rs deleted file mode 100644 index 87c830f6a..000000000 --- a/core/src/file/indexer/scan.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::job::JobResult; -use crate::library::LibraryContext; -use crate::sys::{create_location, LocationResource}; -use chrono::{DateTime, Utc}; -use log::{error, info}; -use prisma_client_rust::prisma_models::PrismaValue; -use prisma_client_rust::raw; -use prisma_client_rust::raw::Raw; -use serde::{Deserialize, Serialize}; -use std::ffi::OsStr; -use std::fmt::Debug; -use std::{ - collections::HashMap, - path::{Path, PathBuf}, - time::Instant, -}; -use tokio::fs; -use walkdir::{DirEntry, WalkDir}; - -#[derive(Clone)] -pub enum ScanProgress { - ChunkCount(usize), - SavedChunks(usize), - Message(String), -} - -static BATCH_SIZE: usize = 100; - -// creates a vector of valid path buffers from a directory -pub async fn scan_path( - ctx: &LibraryContext, - path: impl AsRef + Debug, - on_progress: impl Fn(Vec) + Send + Sync + 'static, -) -> JobResult { - let location = create_location(ctx, &path).await?; - - // query db to highers id, so we can increment it for the new files indexed - #[derive(Deserialize, Serialize, Debug)] - struct QueryRes { - id: Option, - } - // grab the next id so we can increment in memory for batch inserting - let first_file_id = match ctx - .db - ._query_raw::(raw!("SELECT MAX(id) id FROM file_paths")) - .await - { - Ok(rows) => rows[0].id.unwrap_or(0), - Err(e) => panic!("Error querying for next file id: {:#?}", e), - }; - - //check is path is a directory - if !path.as_ref().is_dir() { - // return Err(anyhow::anyhow!("{} is not a directory", &path)); - panic!("{:#?} is not a directory", path); - } - - let path_buf = path.as_ref().to_path_buf(); - - // spawn a dedicated thread to scan the directory for performance - let (paths, scan_start, on_progress) = tokio::task::spawn_blocking(move || { - // store every valid path discovered - let mut paths: Vec<(PathBuf, i32, Option, bool)> = Vec::new(); - // store a hashmap of directories to their file ids for fast lookup - let mut dirs: HashMap = HashMap::new(); - // begin timer for logging purposes - let scan_start = Instant::now(); - - let mut next_file_id = first_file_id; - let mut get_id = || { - next_file_id += 1; - next_file_id - }; - // walk through directory recursively - for entry in WalkDir::new(path_buf).into_iter().filter_entry(|dir| { - // check if entry is approved - !is_hidden(dir) && !is_app_bundle(dir) && !is_node_modules(dir) && !is_library(dir) - }) { - // extract directory entry or log and continue if failed - let entry = match entry { - Ok(entry) => entry, - Err(e) => { - error!("Error reading file {}", e); - continue; - } - }; - let path = entry.path(); - - info!("Found filesystem path: {:?}", path); - - let parent_path = path - .parent() - .unwrap_or_else(|| Path::new("")) - .to_str() - .unwrap_or(""); - let parent_dir_id = dirs.get(&*parent_path); - - let path_str = match path.as_os_str().to_str() { - Some(path_str) => path_str, - None => { - error!("Error reading file {}", &path.display()); - continue; - } - }; - - on_progress(vec![ - ScanProgress::Message(format!("Scanning {}", path_str)), - ScanProgress::ChunkCount(paths.len() / BATCH_SIZE), - ]); - - let file_id = get_id(); - let file_type = entry.file_type(); - let is_dir = file_type.is_dir(); - - if is_dir || file_type.is_file() { - paths.push((path.to_owned(), file_id, parent_dir_id.cloned(), is_dir)); - } - - if is_dir { - let _path = match path.to_str() { - Some(path) => path.to_owned(), - None => continue, - }; - dirs.insert(_path, file_id); - } - } - (paths, scan_start, on_progress) - }) - .await?; - - let db_write_start = Instant::now(); - let scan_read_time = scan_start.elapsed(); - - for (i, chunk) in paths.chunks(BATCH_SIZE).enumerate() { - on_progress(vec![ - ScanProgress::SavedChunks(i as usize), - ScanProgress::Message(format!( - "Writing {} of {} to db", - i * chunk.len(), - paths.len(), - )), - ]); - - // vector to store active models - let mut files: Vec = Vec::new(); - - for (file_path, file_id, parent_dir_id, is_dir) in chunk { - files.extend( - match prepare_values(file_path, *file_id, &location, parent_dir_id, *is_dir).await { - Ok(values) => values.to_vec(), - Err(e) => { - error!("Error creating file model from path {:?}: {}", file_path, e); - continue; - } - }, - ); - } - - let raw = Raw::new( - &format!(" - INSERT INTO file_paths (id, is_dir, location_id, materialized_path, name, extension, parent_id, date_created) - VALUES {} - ", - vec!["({}, {}, {}, {}, {}, {}, {}, {})"; chunk.len()].join(", ") - ), - files - ); - - let count = ctx.db._execute_raw(raw).await; - - info!("Inserted {:?} records", count); - } - info!( - "scan of {:?} completed in {:?}. {:?} files found. db write completed in {:?}", - &path, - scan_read_time, - paths.len(), - db_write_start.elapsed() - ); - Ok(()) -} - -// reads a file at a path and creates an ActiveModel with metadata -async fn prepare_values( - file_path: &PathBuf, - id: i32, - location: &LocationResource, - parent_id: &Option, - is_dir: bool, -) -> Result<[PrismaValue; 8], std::io::Error> { - let metadata = fs::metadata(&file_path).await?; - let location_path = Path::new(location.path.as_ref().unwrap().as_str()); - // let size = metadata.len(); - let name; - let extension; - let date_created: DateTime = metadata.created().unwrap().into(); - - // if the 'file_path' is not a directory, then get the extension and name. - - // if 'file_path' is a directory, set extension to an empty string to avoid periods in folder names - // - being interpreted as file extensions - if is_dir { - extension = "".to_string(); - name = extract_name(file_path.file_name()); - } else { - extension = extract_name(file_path.extension()); - name = extract_name(file_path.file_stem()); - } - - let materialized_path = file_path.strip_prefix(location_path).unwrap(); - let materialized_path_as_string = materialized_path.to_str().unwrap_or("").to_owned(); - - let values = [ - PrismaValue::Int(id as i64), - PrismaValue::Boolean(metadata.is_dir()), - PrismaValue::Int(location.id as i64), - PrismaValue::String(materialized_path_as_string), - PrismaValue::String(name), - PrismaValue::String(extension.to_lowercase()), - parent_id - .map(|id| PrismaValue::Int(id as i64)) - .unwrap_or(PrismaValue::Null), - PrismaValue::DateTime(date_created.into()), - ]; - - Ok(values) -} - -// extract name from OsStr returned by PathBuff -fn extract_name(os_string: Option<&OsStr>) -> String { - os_string - .unwrap_or_default() - .to_str() - .unwrap_or_default() - .to_owned() -} - -fn is_hidden(entry: &DirEntry) -> bool { - entry - .file_name() - .to_str() - .map(|s| s.starts_with('.')) - .unwrap_or(false) -} - -fn is_library(entry: &DirEntry) -> bool { - entry - .path() - .to_str() - // make better this is shit - .map(|s| s.contains("/Library/")) - .unwrap_or(false) -} - -fn is_node_modules(entry: &DirEntry) -> bool { - entry - .file_name() - .to_str() - .map(|s| s.contains("node_modules")) - .unwrap_or(false) -} - -fn is_app_bundle(entry: &DirEntry) -> bool { - let is_dir = entry.metadata().unwrap().is_dir(); - let contains_dot = entry - .file_name() - .to_str() - .map(|s| s.contains(".app") | s.contains(".bundle")) - .unwrap_or(false); - - // let is_app_bundle = is_dir && contains_dot; - // if is_app_bundle { - // let path_buff = entry.path(); - // let path = path_buff.to_str().unwrap(); - - // self::path(&path, ); - // } - - is_dir && contains_dot -} diff --git a/core/src/file/mod.rs b/core/src/file/mod.rs index 6def304ad..a642eb7c8 100644 --- a/core/src/file/mod.rs +++ b/core/src/file/mod.rs @@ -1,3 +1,10 @@ +use crate::{ + library::LibraryContext, + prisma::{self, file, file_path}, + sys::SysError, + ClientQuery, CoreError, CoreEvent, CoreResponse, LibraryQuery, +}; + use chrono::{DateTime, Utc}; use int_enum::IntEnum; use serde::{Deserialize, Serialize}; @@ -5,12 +12,6 @@ use std::path::PathBuf; use thiserror::Error; use ts_rs::TS; -use crate::{ - library::LibraryContext, - prisma::{self, file, file_path}, - sys::SysError, - ClientQuery, CoreError, CoreEvent, CoreResponse, LibraryQuery, -}; pub mod cas; pub mod explorer; pub mod indexer; @@ -58,9 +59,9 @@ pub struct FilePath { pub file_id: Option, pub parent_id: Option, - pub date_created: DateTime, - pub date_modified: DateTime, - pub date_indexed: DateTime, + pub date_created: DateTime, + pub date_modified: DateTime, + pub date_indexed: DateTime, pub file: Option, } @@ -163,15 +164,7 @@ pub async fn set_note( .await .unwrap(); - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), - query: LibraryQuery::GetExplorerDir { - limit: 0, - path: PathBuf::new(), - location_id: 0, - }, - })) - .await; + send_invalidate_query(&ctx).await; Ok(CoreResponse::Success(())) } @@ -190,8 +183,14 @@ pub async fn favorite( .await .unwrap(); + send_invalidate_query(&ctx).await; + + Ok(CoreResponse::Success(())) +} + +async fn send_invalidate_query(ctx: &LibraryContext) { ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), + library_id: ctx.id, query: LibraryQuery::GetExplorerDir { limit: 0, path: PathBuf::new(), @@ -199,6 +198,4 @@ pub async fn favorite( }, })) .await; - - Ok(CoreResponse::Success(())) } diff --git a/core/src/job/jobs.rs b/core/src/job/job_manager.rs similarity index 62% rename from core/src/job/jobs.rs rename to core/src/job/job_manager.rs index 0cca9b8ad..512885971 100644 --- a/core/src/job/jobs.rs +++ b/core/src/job/job_manager.rs @@ -1,57 +1,61 @@ -use super::{ - worker::{Worker, WorkerContext}, - JobError, -}; use crate::{ + encode::THUMBNAIL_JOB_NAME, + file::{ + cas::IDENTIFIER_JOB_NAME, + indexer::{IndexerJob, INDEXER_JOB_NAME}, + }, + job::{worker::Worker, DynJob, JobError}, library::LibraryContext, prisma::{job, node}, + FileIdentifierJob, Job, ThumbnailJob, }; use int_enum::IntEnum; use log::{error, info}; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, VecDeque}, - error::Error, fmt::Debug, + fmt::{Display, Formatter}, sync::Arc, + time::Duration, +}; +use tokio::{ + sync::{broadcast, mpsc, Mutex, RwLock}, + time::sleep, }; -use tokio::sync::{mpsc, Mutex, RwLock}; use ts_rs::TS; +use uuid::Uuid; // db is single threaded, nerd const MAX_WORKERS: usize = 1; -pub type JobResult = Result<(), Box>; - -#[async_trait::async_trait] -pub trait Job: Send + Sync + Debug { - fn name(&self) -> &'static str; - async fn run(&self, ctx: WorkerContext) -> JobResult; -} - pub enum JobManagerEvent { - IngestJob(LibraryContext, Box), + IngestJob(LibraryContext, Box), } // jobs struct is maintained by the core pub struct JobManager { - job_queue: RwLock>>, + job_queue: RwLock>>, // workers are spawned when jobs are picked off the queue - running_workers: RwLock>>>, + running_workers: RwLock>>>, internal_sender: mpsc::UnboundedSender, + shutdown_tx: Arc>, } impl JobManager { pub fn new() -> Arc { + let (shutdown_tx, _shutdown_rx) = broadcast::channel(1); let (internal_sender, mut internal_receiver) = mpsc::unbounded_channel(); let this = Arc::new(Self { job_queue: RwLock::new(VecDeque::new()), running_workers: RwLock::new(HashMap::new()), internal_sender, + shutdown_tx: Arc::new(shutdown_tx), }); let this2 = this.clone(); tokio::spawn(async move { + // FIXME: if this task crashes, the entire application is unusable while let Some(event) = internal_receiver.recv().await { match event { JobManagerEvent::IngestJob(ctx, job) => this2.clone().ingest(&ctx, job).await, @@ -62,30 +66,36 @@ impl JobManager { this } - pub async fn ingest(self: Arc, ctx: &LibraryContext, job: Box) { + pub async fn ingest(self: Arc, ctx: &LibraryContext, mut job: Box) { // create worker to process job let mut running_workers = self.running_workers.write().await; if running_workers.len() < MAX_WORKERS { info!("Running job: {:?}", job.name()); - let worker = Worker::new(job); - let id = worker.id(); + let job_report = job + .report() + .take() + .expect("critical error: missing job on worker"); + + let job_id = job_report.id; + + let worker = Worker::new(job, job_report); let wrapped_worker = Arc::new(Mutex::new(worker)); Worker::spawn(Arc::clone(&self), Arc::clone(&wrapped_worker), ctx.clone()).await; - running_workers.insert(id, wrapped_worker); + running_workers.insert(job_id, wrapped_worker); } else { self.job_queue.write().await.push_back(job); } } - pub async fn ingest_queue(&self, _ctx: &LibraryContext, job: Box) { + pub async fn ingest_queue(&self, _ctx: &LibraryContext, job: Box) { self.job_queue.write().await.push_back(job); } - pub async fn complete(self: Arc, ctx: &LibraryContext, job_id: String) { + pub async fn complete(self: Arc, ctx: &LibraryContext, job_id: Uuid) { // remove worker from running workers self.running_workers.write().await.remove(&job_id); // continue queue @@ -105,7 +115,7 @@ impl JobManager { for worker in self.running_workers.read().await.values() { let worker = worker.lock().await; - ret.push(worker.job_report.clone()); + ret.push(worker.report()); } ret } @@ -131,6 +141,72 @@ impl JobManager { Ok(jobs.into_iter().map(Into::into).collect()) } + + pub fn shutdown_tx(&self) -> Arc> { + Arc::clone(&self.shutdown_tx) + } + + pub async fn pause(&self) { + let running_workers_read_guard = self.running_workers.read().await; + if !running_workers_read_guard.is_empty() { + self.shutdown_tx + .send(()) + .expect("Failed to send shutdown signal"); + } + // Dropping our handle so jobs can finish + drop(running_workers_read_guard); + + loop { + sleep(Duration::from_millis(50)).await; + if self.running_workers.read().await.is_empty() { + break; + } + } + } + + pub async fn resume_jobs(self: Arc, ctx: &LibraryContext) -> Result<(), JobError> { + let paused_jobs = ctx + .db + .job() + .find_many(vec![job::status::equals(JobStatus::Paused.int_value())]) + .exec() + .await?; + + for paused_job_data in paused_jobs { + let paused_job = JobReport::from(paused_job_data); + + info!("Resuming job: {}, id: {}", paused_job.name, paused_job.id); + match paused_job.name.as_str() { + THUMBNAIL_JOB_NAME => { + Arc::clone(&self) + .ingest(ctx, Job::resume(paused_job, Box::new(ThumbnailJob {}))?) + .await; + } + INDEXER_JOB_NAME => { + Arc::clone(&self) + .ingest(ctx, Job::resume(paused_job, Box::new(IndexerJob {}))?) + .await; + } + IDENTIFIER_JOB_NAME => { + Arc::clone(&self) + .ingest( + ctx, + Job::resume(paused_job, Box::new(FileIdentifierJob {}))?, + ) + .await; + } + _ => { + error!( + "Unknown job type: {}, id: {}", + paused_job.name, paused_job.id + ); + return Err(JobError::UnknownJobName(paused_job.id, paused_job.name)); + } + }; + } + + Ok(()) + } } #[derive(Debug)] @@ -144,9 +220,9 @@ pub enum JobReportUpdate { #[derive(Debug, Serialize, Deserialize, TS, Clone)] #[ts(export)] pub struct JobReport { - pub id: String, + pub id: Uuid, pub name: String, - pub data: Option, + pub data: Option>, // client_id: i32, #[ts(type = "string")] pub date_created: chrono::DateTime, @@ -163,11 +239,21 @@ pub struct JobReport { pub seconds_elapsed: i32, } +impl Display for JobReport { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Job {:#?}", + self.name, self.id, self.status + ) + } +} + // convert database struct into a resource struct impl From for JobReport { fn from(data: job::Data) -> JobReport { JobReport { - id: data.id, + id: Uuid::from_slice(&data.id).unwrap(), name: data.name, // client_id: data.client_id, status: JobStatus::from_int(data.status).unwrap(), @@ -183,7 +269,7 @@ impl From for JobReport { } impl JobReport { - pub fn new(uuid: String, name: String) -> Self { + pub fn new(uuid: Uuid, name: String) -> Self { Self { id: uuid, name, @@ -209,7 +295,7 @@ impl JobReport { ctx.db .job() .create( - job::id::set(self.id.clone()), + job::id::set(self.id.as_bytes().to_vec()), job::name::set(self.name.clone()), job::action::set(1), job::nodes::link(node::id::equals(ctx.node_local_id)), @@ -222,9 +308,10 @@ impl JobReport { pub async fn update(&self, ctx: &LibraryContext) -> Result<(), JobError> { ctx.db .job() - .find_unique(job::id::equals(self.id.clone())) + .find_unique(job::id::equals(self.id.as_bytes().to_vec())) .update(vec![ job::status::set(self.status.int_value()), + job::data::set(self.data.clone()), job::task_count::set(self.task_count), job::completed_task_count::set(self.completed_task_count), job::date_modified::set(chrono::Utc::now().into()), @@ -245,4 +332,5 @@ pub enum JobStatus { Completed = 2, Canceled = 3, Failed = 4, + Paused = 5, } diff --git a/core/src/job/mod.rs b/core/src/job/mod.rs index c337a037b..6099a78b3 100644 --- a/core/src/job/mod.rs +++ b/core/src/job/mod.rs @@ -1,18 +1,187 @@ -use std::fmt::Debug; +use crate::{file::FileError, prisma, sys::SysError}; +use rmp_serde::{decode::Error as DecodeError, encode::Error as EncodeError}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{collections::VecDeque, fmt::Debug}; use thiserror::Error; +use uuid::Uuid; -use crate::prisma; - -mod jobs; +mod job_manager; mod worker; -pub use jobs::*; +pub use job_manager::*; pub use worker::*; #[derive(Error, Debug)] pub enum JobError { - #[error("Failed to create job (job_id {job_id:?})")] - CreateFailure { job_id: String }, - #[error("Database error")] + #[error("Database error: {0}")] DatabaseError(#[from] prisma::QueryError), + #[error("System error: {0}")] + SystemError(#[from] SysError), + #[error("I/O error: {0}")] + IOError(#[from] std::io::Error), + #[error("Failed to join Tokio spawn blocking: {0}")] + JoinError(#[from] tokio::task::JoinError), + #[error("File error: {0}")] + FileError(#[from] FileError), + #[error("Job state encode error: {0}")] + StateEncode(#[from] EncodeError), + #[error("Job state decode error: {0}")] + StateDecode(#[from] DecodeError), + #[error("Tried to resume a job with unknown name: job ")] + UnknownJobName(Uuid, String), + #[error( + "Tried to resume a job that doesn't have saved state data: job " + )] + MissingJobDataState(Uuid, String), + #[error("Job paused")] + Paused(Vec), +} + +pub type JobResult = Result<(), JobError>; + +#[async_trait::async_trait] +pub trait StatefulJob: Send + Sync { + type Init: Serialize + DeserializeOwned + Send + Sync; + type Data: Serialize + DeserializeOwned + Send + Sync; + type Step: Serialize + DeserializeOwned + Send + Sync; + + fn name(&self) -> &'static str; + async fn init( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult; + + async fn execute_step( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult; + + async fn finalize( + &self, + ctx: WorkerContext, + state: &mut JobState, + ) -> JobResult; +} + +#[async_trait::async_trait] +pub trait DynJob: Send + Sync { + fn report(&mut self) -> &mut Option; + fn name(&self) -> &'static str; + async fn run(&mut self, ctx: WorkerContext) -> JobResult; +} + +pub struct Job +where + Init: Serialize + DeserializeOwned + Send + Sync, + Data: Serialize + DeserializeOwned + Send + Sync, + Step: Serialize + DeserializeOwned + Send + Sync, +{ + report: Option, + state: JobState, + stateful_job: Box>, +} + +impl Job +where + Init: Serialize + DeserializeOwned + Send + Sync, + Data: Serialize + DeserializeOwned + Send + Sync, + Step: Serialize + DeserializeOwned + Send + Sync, +{ + pub fn new( + init: Init, + stateful_job: Box>, + ) -> Box { + Box::new(Self { + report: Some(JobReport::new( + Uuid::new_v4(), + stateful_job.name().to_string(), + )), + state: JobState { + init, + data: None, + steps: VecDeque::new(), + step_number: 0, + }, + stateful_job, + }) + } + + pub fn resume( + mut report: JobReport, + stateful_job: Box>, + ) -> Result, JobError> { + let job_state_data = if let Some(data) = report.data.take() { + data + } else { + return Err(JobError::MissingJobDataState(report.id, report.name)); + }; + + Ok(Box::new(Self { + report: Some(report), + state: rmp_serde::from_slice(&job_state_data)?, + stateful_job, + })) + } +} + +#[derive(Serialize, Deserialize)] +pub struct JobState { + pub init: Init, + pub data: Option, + pub steps: VecDeque, + pub step_number: usize, +} + +#[async_trait::async_trait] +impl DynJob for Job +where + Init: Serialize + DeserializeOwned + Send + Sync, + Data: Serialize + DeserializeOwned + Send + Sync, + Step: Serialize + DeserializeOwned + Send + Sync, +{ + fn report(&mut self) -> &mut Option { + &mut self.report + } + + fn name(&self) -> &'static str { + self.stateful_job.name() + } + async fn run(&mut self, ctx: WorkerContext) -> JobResult { + // Checking if we have a brand new job, or if we are resuming an old one. + if self.state.data.is_none() { + self.stateful_job.init(ctx.clone(), &mut self.state).await?; + } + + let mut shutdown_rx = ctx.shutdown_rx(); + let shutdown_rx_fut = shutdown_rx.recv(); + tokio::pin!(shutdown_rx_fut); + + while !self.state.steps.is_empty() { + tokio::select! { + step_result = self.stateful_job.execute_step( + ctx.clone(), + &mut self.state, + ) => { + step_result?; + self.state.steps.pop_front(); + } + _ = &mut shutdown_rx_fut => { + return Err( + JobError::Paused( + rmp_serde::to_vec(&self.state)? + ) + ); + } + } + self.state.step_number += 1; + } + + self.stateful_job + .finalize(ctx.clone(), &mut self.state) + .await?; + + Ok(()) + } } diff --git a/core/src/job/worker.rs b/core/src/job/worker.rs index 143335786..1049ea8b5 100644 --- a/core/src/job/worker.rs +++ b/core/src/job/worker.rs @@ -1,208 +1,255 @@ -use super::{ - jobs::{JobReport, JobReportUpdate, JobStatus}, - Job, JobManager, +use crate::{ + job::{DynJob, JobError, JobManager, JobReportUpdate, JobStatus}, + library::LibraryContext, + ClientQuery, CoreEvent, JobReport, LibraryQuery, }; -use crate::{library::LibraryContext, ClientQuery, CoreEvent, LibraryQuery}; -use log::error; +use log::{error, info, warn}; use std::{sync::Arc, time::Duration}; use tokio::{ sync::{ + broadcast, mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, Mutex, }, - time::{sleep, Instant}, + time::{interval_at, Instant}, }; -use uuid::Uuid; // used to update the worker state from inside the worker thread +#[derive(Debug)] pub enum WorkerEvent { Progressed(Vec), Completed, Failed, -} - -enum WorkerState { - Pending(Box, UnboundedReceiver), - Running, + Paused(Vec), } #[derive(Clone)] pub struct WorkerContext { - pub uuid: String, library_ctx: LibraryContext, - sender: UnboundedSender, + events_tx: UnboundedSender, + shutdown_tx: Arc>, } impl WorkerContext { pub fn progress(&self, updates: Vec) { - self.sender + self.events_tx .send(WorkerEvent::Progressed(updates)) - .unwrap_or(()); + .expect("critical error: failed to send worker worker progress event updates"); } pub fn library_ctx(&self) -> LibraryContext { self.library_ctx.clone() } - // save the job data to - // pub fn save_data () { - // } + pub fn shutdown_rx(&self) -> broadcast::Receiver<()> { + self.shutdown_tx.subscribe() + } } // a worker is a dedicated thread that runs a single job // once the job is complete the worker will exit pub struct Worker { - pub job_report: JobReport, - state: WorkerState, - worker_sender: UnboundedSender, + job: Option>, + report: JobReport, + worker_events_tx: UnboundedSender, + worker_events_rx: Option>, } impl Worker { - pub fn new(job: Box) -> Self { - let (worker_sender, worker_receiver) = unbounded_channel(); - let uuid = Uuid::new_v4().to_string(); - let name = job.name(); + pub fn new(job: Box, report: JobReport) -> Self { + let (worker_events_tx, worker_events_rx) = unbounded_channel(); Self { - state: WorkerState::Pending(job, worker_receiver), - job_report: JobReport::new(uuid, name.to_string()), - worker_sender, + job: Some(job), + report, + worker_events_tx, + worker_events_rx: Some(worker_events_rx), } } + + pub fn report(&self) -> JobReport { + self.report.clone() + } // spawns a thread and extracts channel sender to communicate with it pub async fn spawn( job_manager: Arc, - worker: Arc>, + worker_mutex: Arc>, ctx: LibraryContext, ) { + let mut worker = worker_mutex.lock().await; // we capture the worker receiver channel so state can be updated from inside the worker - let mut worker_mut = worker.lock().await; - // extract owned job and receiver from Self - let (job, worker_receiver) = - match std::mem::replace(&mut worker_mut.state, WorkerState::Running) { - WorkerState::Pending(job, worker_receiver) => { - worker_mut.state = WorkerState::Running; - (job, worker_receiver) - } - WorkerState::Running => unreachable!(), - }; - let worker_sender = worker_mut.worker_sender.clone(); + let worker_events_tx = worker.worker_events_tx.clone(); + let worker_events_rx = worker + .worker_events_rx + .take() + .expect("critical error: missing worker events rx"); - worker_mut.job_report.status = JobStatus::Running; + let mut job = worker + .job + .take() + .expect("critical error: missing job on worker"); - worker_mut.job_report.create(&ctx).await.unwrap_or(()); + let job_id = worker.report.id; + let old_status = worker.report.status; + worker.report.status = JobStatus::Running; + if matches!(old_status, JobStatus::Queued) { + worker.report.create(&ctx).await.unwrap_or(()); + } + drop(worker); // spawn task to handle receiving events from the worker let library_ctx = ctx.clone(); tokio::spawn(Worker::track_progress( - worker.clone(), - worker_receiver, + Arc::clone(&worker_mutex), + worker_events_rx, library_ctx.clone(), )); - let uuid = worker_mut.job_report.id.clone(); // spawn task to handle running the job - tokio::spawn(async move { let worker_ctx = WorkerContext { - uuid, library_ctx, - sender: worker_sender, + events_tx: worker_events_tx, + shutdown_tx: job_manager.shutdown_tx(), }; - let job_start = Instant::now(); // track time - let sender = worker_ctx.sender.clone(); + let events_tx = worker_ctx.events_tx.clone(); tokio::spawn(async move { + let mut interval = interval_at( + Instant::now() + Duration::from_millis(1000), + Duration::from_millis(1000), + ); loop { - let elapsed = job_start.elapsed().as_secs(); - sender + interval.tick().await; + if events_tx .send(WorkerEvent::Progressed(vec![ - JobReportUpdate::SecondsElapsed(elapsed), + JobReportUpdate::SecondsElapsed(1), ])) - .unwrap_or(()); - sleep(Duration::from_millis(1000)).await; + .is_err() && events_tx.is_closed() + { + break; + } } }); if let Err(e) = job.run(worker_ctx.clone()).await { - error!("job '{}' failed with error: {}", worker_ctx.uuid, e); - worker_ctx.sender.send(WorkerEvent::Failed).unwrap_or(()); + if let JobError::Paused(state) = e { + worker_ctx + .events_tx + .send(WorkerEvent::Paused(state)) + .expect("critical error: failed to send worker pause event"); + } else { + error!("job '{}' failed with error: {:#?}", job_id, e); + worker_ctx + .events_tx + .send(WorkerEvent::Failed) + .expect("critical error: failed to send worker fail event"); + } } else { // handle completion - worker_ctx.sender.send(WorkerEvent::Completed).unwrap_or(()); + worker_ctx + .events_tx + .send(WorkerEvent::Completed) + .expect("critical error: failed to send worker complete event"); } - job_manager.complete(&ctx, worker_ctx.uuid).await; + job_manager.complete(&ctx, job_id).await; }); } - pub fn id(&self) -> String { - self.job_report.id.to_owned() - } - async fn track_progress( worker: Arc>, - mut channel: UnboundedReceiver, + mut worker_events_rx: UnboundedReceiver, ctx: LibraryContext, ) { - while let Some(command) = channel.recv().await { + while let Some(command) = worker_events_rx.recv().await { let mut worker = worker.lock().await; match command { WorkerEvent::Progressed(changes) => { // protect against updates if job is not running - if worker.job_report.status != JobStatus::Running { + if worker.report.status != JobStatus::Running { continue; }; for change in changes { match change { JobReportUpdate::TaskCount(task_count) => { - worker.job_report.task_count = task_count as i32; + worker.report.task_count = task_count as i32; } JobReportUpdate::CompletedTaskCount(completed_task_count) => { - worker.job_report.completed_task_count = - completed_task_count as i32; + worker.report.completed_task_count = completed_task_count as i32; } JobReportUpdate::Message(message) => { - worker.job_report.message = message; + worker.report.message = message; } JobReportUpdate::SecondsElapsed(seconds) => { - worker.job_report.seconds_elapsed = seconds as i32; + worker.report.seconds_elapsed += seconds as i32; } } } ctx.emit(CoreEvent::InvalidateQueryDebounced( ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), + library_id: ctx.id, query: LibraryQuery::GetRunningJobs, }, )) .await; } WorkerEvent::Completed => { - worker.job_report.status = JobStatus::Completed; - worker.job_report.update(&ctx).await.unwrap_or(()); + worker.report.status = JobStatus::Completed; + worker.report.data = None; + worker + .report + .update(&ctx) + .await + .expect("critical error: failed to update job report"); ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), + library_id: ctx.id, query: LibraryQuery::GetRunningJobs, })) .await; ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), + library_id: ctx.id, query: LibraryQuery::GetJobHistory, })) .await; + info!("{}", worker.report); + break; } WorkerEvent::Failed => { - worker.job_report.status = JobStatus::Failed; - worker.job_report.update(&ctx).await.unwrap_or(()); + worker.report.status = JobStatus::Failed; + worker.report.data = None; + worker + .report + .update(&ctx) + .await + .expect("critical error: failed to update job report"); ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), + library_id: ctx.id, + query: LibraryQuery::GetJobHistory, + })) + .await; + warn!("{}", worker.report); + + break; + } + WorkerEvent::Paused(state) => { + worker.report.status = JobStatus::Paused; + worker.report.data = Some(state); + worker + .report + .update(&ctx) + .await + .expect("critical error: failed to update job report"); + info!("{}", worker.report); + + ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { + library_id: ctx.id, query: LibraryQuery::GetJobHistory, })) .await; diff --git a/core/src/lib.rs b/core/src/lib.rs index 05cda65a0..3526be1a1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,15 +1,19 @@ -use crate::{file::cas::FileIdentifierJob, prisma::file as prisma_file, prisma::location}; -use job::{JobManager, JobReport}; -use library::{LibraryConfig, LibraryConfigWrapped, LibraryManager}; -use log::error; -use node::{NodeConfig, NodeConfigManager}; +use crate::{ + encode::{ThumbnailJob, ThumbnailJobInit}, + file::cas::{FileIdentifierJob, FileIdentifierJobInit}, + job::{Job, JobManager, JobReport}, + library::{LibraryConfig, LibraryConfigWrapped, LibraryManager}, + node::{NodeConfig, NodeConfigManager}, + prisma::file as prisma_file, + prisma::location, + tag::{Tag, TagWithFiles}, +}; +use log::{error, info}; use serde::{Deserialize, Serialize}; use std::{ path::{Path, PathBuf}, sync::Arc, }; -use tag::{Tag, TagWithFiles}; - use thiserror::Error; use tokio::{ fs, @@ -19,8 +23,7 @@ use tokio::{ }, }; use ts_rs::TS; - -use crate::encode::ThumbnailJob; +use uuid::Uuid; mod encode; mod file; @@ -56,7 +59,7 @@ impl NodeController { }) .unwrap_or(()); // wait for response and return - recv.await.unwrap_or(Err(CoreError::QueryError)) + recv.await.unwrap_or(Err(CoreError::Query)) } pub async fn command(&self, command: ClientCommand) -> Result { @@ -102,35 +105,56 @@ pub struct Node { UnboundedReceiver>, ), event_sender: mpsc::Sender, + shutdown_completion_tx: oneshot::Sender<()>, } impl Node { // create new instance of node, run startup tasks pub async fn new( data_dir: impl AsRef, - ) -> (NodeController, mpsc::Receiver, Node) { - fs::create_dir_all(&data_dir).await.unwrap(); + ) -> ( + NodeController, + mpsc::Receiver, + Node, + oneshot::Receiver<()>, + ) { + let data_dir = data_dir.as_ref(); + fs::create_dir_all(data_dir).await.unwrap(); let (event_sender, event_recv) = mpsc::channel(100); - let config = NodeConfigManager::new(data_dir.as_ref().to_owned()) - .await - .unwrap(); + let config = NodeConfigManager::new(data_dir.to_owned()).await.unwrap(); + + let (shutdown_completion_tx, shutdown_completion_rx) = oneshot::channel(); + let jobs = JobManager::new(); let node_ctx = NodeContext { event_sender: event_sender.clone(), config: config.clone(), jobs: jobs.clone(), }; + let library_manager = LibraryManager::new(data_dir.join("libraries"), node_ctx) + .await + .unwrap(); + + // Trying to resume possible paused jobs + let inner_library_manager = Arc::clone(&library_manager); + let inner_jobs = Arc::clone(&jobs); + tokio::spawn(async move { + for library_ctx in inner_library_manager.get_all_libraries_ctx().await { + if let Err(e) = Arc::clone(&inner_jobs).resume_jobs(&library_ctx).await { + error!("Failed to resume jobs for library. {:#?}", e); + } + } + }); let node = Node { config, - library_manager: LibraryManager::new(data_dir.as_ref().join("libraries"), node_ctx) - .await - .unwrap(), + library_manager, query_channel: unbounded_channel(), command_channel: unbounded_channel(), jobs, event_sender, + shutdown_completion_tx, }; ( @@ -140,6 +164,7 @@ impl Node { }, event_recv, node, + shutdown_completion_rx, ) } @@ -151,7 +176,7 @@ impl Node { } } - pub async fn start(mut self) { + pub async fn start(mut self, mut shutdown_rx: oneshot::Receiver<()>) { loop { // listen on global messaging channels for incoming messages tokio::select! { @@ -163,10 +188,24 @@ impl Node { let res = self.exec_command(msg.data).await; msg.return_sender.send(res).unwrap_or(()); } + + _ = &mut shutdown_rx => { + info!("Initiating shutdown node..."); + self.shutdown().await; + info!("Node shutdown complete."); + self.shutdown_completion_tx.send(()) + .expect("critical error: failed to send node shutdown completion signal"); + + break; + } } } } + pub async fn shutdown(&self) { + self.jobs.pause().await + } + async fn exec_command(&mut self, cmd: ClientCommand) -> Result { Ok(match cmd { ClientCommand::CreateLibrary { name } => { @@ -258,19 +297,25 @@ impl Node { // CRUD for libraries LibraryCommand::VolUnmount { id: _ } => todo!(), LibraryCommand::GenerateThumbsForLocation { id, path } => { - ctx.spawn_job(Box::new(ThumbnailJob { - location_id: id, - path, - background: false, // fix - })) + ctx.spawn_job(Job::new( + ThumbnailJobInit { + location_id: id, + path, + background: false, // fix + }, + Box::new(ThumbnailJob {}), + )) .await; CoreResponse::Success(()) } LibraryCommand::IdentifyUniqueFiles { id, path } => { - ctx.spawn_job(Box::new(FileIdentifierJob { - location_id: id, - path, - })) + ctx.spawn_job(Job::new( + FileIdentifierJobInit { + location_id: id, + path, + }, + Box::new(FileIdentifierJob {}), + )) .await; CoreResponse::Success(()) } @@ -292,7 +337,7 @@ impl Node { ClientQuery::GetNodes => todo!(), ClientQuery::GetVolumes => CoreResponse::GetVolumes(sys::Volume::get_volumes()?), ClientQuery::LibraryQuery { library_id, query } => { - let ctx = match self.library_manager.get_ctx(library_id.clone()).await { + let ctx = match self.library_manager.get_ctx(library_id).await { Some(ctx) => ctx, None => { println!("Library '{}' not found!", library_id); @@ -344,15 +389,15 @@ pub enum ClientCommand { name: String, }, EditLibrary { - id: String, + id: Uuid, name: Option, description: Option, }, DeleteLibrary { - id: String, + id: Uuid, }, LibraryCommand { - library_id: String, + library_id: Uuid, command: LibraryCommand, }, } @@ -437,7 +482,7 @@ pub enum ClientQuery { GetVolumes, GetNodes, LibraryQuery { - library_id: String, + library_id: Uuid, query: LibraryQuery, }, } @@ -512,17 +557,17 @@ pub enum CoreResponse { #[derive(Error, Debug)] pub enum CoreError { #[error("Query error")] - QueryError, - #[error("System error")] - SysError(#[from] sys::SysError), - #[error("File error")] - FileError(#[from] file::FileError), - #[error("Job error")] - JobError(#[from] job::JobError), - #[error("Database error")] - DatabaseError(#[from] prisma::QueryError), - #[error("Database error")] - LibraryError(#[from] library::LibraryError), + Query, + #[error("System error: {0}")] + Sys(#[from] sys::SysError), + #[error("File error: {0}")] + File(#[from] file::FileError), + #[error("Job error: {0}")] + Job(#[from] job::JobError), + #[error("Database error: {0}")] + Database(#[from] prisma::QueryError), + #[error("Library error: {0}")] + Library(#[from] library::LibraryError), } #[derive(Serialize, Deserialize, Debug, Clone, TS)] diff --git a/core/src/library/library_config.rs b/core/src/library/library_config.rs index f3ab140f1..d9a0db90a 100644 --- a/core/src/library/library_config.rs +++ b/core/src/library/library_config.rs @@ -7,6 +7,7 @@ use std::{ use serde::{Deserialize, Serialize}; use std::io::Write; use ts_rs::TS; +use uuid::Uuid; use crate::node::ConfigMetadata; @@ -64,6 +65,6 @@ impl LibraryConfig { #[derive(Serialize, Deserialize, Debug, TS)] #[ts(export)] pub struct LibraryConfigWrapped { - pub uuid: String, + pub uuid: Uuid, pub config: LibraryConfig, } diff --git a/core/src/library/library_ctx.rs b/core/src/library/library_ctx.rs index 50bc5ea94..3526f7498 100644 --- a/core/src/library/library_ctx.rs +++ b/core/src/library/library_ctx.rs @@ -1,9 +1,7 @@ +use crate::{job::DynJob, node::NodeConfigManager, prisma::PrismaClient, CoreEvent, NodeContext}; use std::sync::Arc; - use uuid::Uuid; -use crate::{job::Job, node::NodeConfigManager, prisma::PrismaClient, CoreEvent, NodeContext}; - use super::LibraryConfig; /// LibraryContext holds context for a library which can be passed around the application. @@ -22,11 +20,11 @@ pub struct LibraryContext { } impl LibraryContext { - pub(crate) async fn spawn_job(&self, job: Box) { + pub(crate) async fn spawn_job(&self, job: Box) { self.node_context.jobs.clone().ingest(self, job).await; } - pub(crate) async fn queue_job(&self, job: Box) { + pub(crate) async fn queue_job(&self, job: Box) { self.node_context.jobs.ingest_queue(self, job).await; } diff --git a/core/src/library/library_manager.rs b/core/src/library/library_manager.rs index 89917a4cf..fb17618f2 100644 --- a/core/src/library/library_manager.rs +++ b/core/src/library/library_manager.rs @@ -85,15 +85,7 @@ impl LibraryManager { } let config = LibraryConfig::read(config_path).await?; - libraries.push( - Self::load( - library_id, - db_path.to_str().unwrap(), - config, - node_context.clone(), - ) - .await?, - ); + libraries.push(Self::load(library_id, &db_path, config, node_context.clone()).await?); } let this = Arc::new(Self { @@ -108,8 +100,7 @@ impl LibraryManager { name: "My Default Library".into(), ..Default::default() }) - .await - .unwrap(); + .await?; } Ok(this) @@ -148,14 +139,18 @@ impl LibraryManager { .iter() .map(|lib| LibraryConfigWrapped { config: lib.config.clone(), - uuid: lib.id.to_string(), + uuid: lib.id, }) .collect() } + pub(crate) async fn get_all_libraries_ctx(&self) -> Vec { + self.libraries.read().await.clone() + } + pub(crate) async fn edit( &self, - id: String, + id: Uuid, name: Option, description: Option, ) -> Result<(), LibraryManagerError> { @@ -163,7 +158,7 @@ impl LibraryManager { let mut libraries = self.libraries.write().await; let library = libraries .iter_mut() - .find(|lib| lib.id == Uuid::from_str(&id).unwrap()) + .find(|lib| lib.id == id) .ok_or(LibraryManagerError::LibraryNotFound)?; // update the library @@ -186,11 +181,9 @@ impl LibraryManager { Ok(()) } - pub async fn delete_library(&self, id: String) -> Result<(), LibraryManagerError> { + pub async fn delete_library(&self, id: Uuid) -> Result<(), LibraryManagerError> { let mut libraries = self.libraries.write().await; - let id = Uuid::parse_str(&id)?; - let library = libraries .iter() .find(|l| l.id == id) @@ -208,12 +201,12 @@ impl LibraryManager { } // get_ctx will return the library context for the given library id. - pub(crate) async fn get_ctx(&self, library_id: String) -> Option { + pub(crate) async fn get_ctx(&self, library_id: Uuid) -> Option { self.libraries .read() .await .iter() - .find(|lib| lib.id.to_string() == library_id) + .find(|lib| lib.id == library_id) .map(Clone::clone) } @@ -239,12 +232,14 @@ impl LibraryManager { _ => Platform::Unknown, }; + let uuid_vec = id.as_bytes().to_vec(); + let node_data = db .node() .upsert( - node::pub_id::equals(id.to_string()), + node::pub_id::equals(uuid_vec.clone()), ( - node::pub_id::set(id.to_string()), + node::pub_id::set(uuid_vec), node::name::set(node_config.name.clone()), vec![node::platform::set(platform as i32)], ), diff --git a/core/src/library/mod.rs b/core/src/library/mod.rs index 23aed8efa..7d7c34608 100644 --- a/core/src/library/mod.rs +++ b/core/src/library/mod.rs @@ -1,3 +1,6 @@ +use crate::{prisma, sys::SysError}; +use thiserror::Error; + mod library_config; mod library_ctx; mod library_manager; @@ -8,10 +11,6 @@ pub use library_ctx::*; pub use library_manager::*; pub use statistics::*; -use thiserror::Error; - -use crate::{prisma, sys::SysError}; - #[derive(Error, Debug)] pub enum LibraryError { #[error("Missing library")] diff --git a/core/src/node/config.rs b/core/src/node/config.rs index ea1a09f1a..9a8ba76ac 100644 --- a/core/src/node/config.rs +++ b/core/src/node/config.rs @@ -1,8 +1,10 @@ use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::io::{self, BufReader, Seek, SeekFrom, Write}; -use std::path::{Path, PathBuf}; -use std::sync::Arc; +use std::{ + fs::File, + io::{self, BufReader, Seek, SeekFrom, Write}, + path::{Path, PathBuf}, + sync::Arc, +}; use thiserror::Error; use tokio::sync::{RwLock, RwLockWriteGuard}; use ts_rs::TS; diff --git a/core/src/node/mod.rs b/core/src/node/mod.rs index 5276ab015..c06a3fb8b 100644 --- a/core/src/node/mod.rs +++ b/core/src/node/mod.rs @@ -2,6 +2,8 @@ use chrono::{DateTime, Utc}; use int_enum::IntEnum; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use uuid::Uuid; + mod config; use crate::prisma::node; pub use config::*; @@ -9,7 +11,7 @@ pub use config::*; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] pub struct LibraryNode { - pub uuid: String, + pub uuid: Uuid, pub name: String, pub platform: Platform, pub last_seen: DateTime, @@ -18,7 +20,7 @@ pub struct LibraryNode { impl From for LibraryNode { fn from(data: node::Data) -> Self { Self { - uuid: data.pub_id, + uuid: Uuid::from_slice(&data.pub_id).unwrap(), name: data.name, platform: IntEnum::from_int(data.platform).unwrap(), last_seen: data.last_seen.into(), diff --git a/core/src/sys/locations.rs b/core/src/sys/locations.rs index 2daa65c3d..764e1a97a 100644 --- a/core/src/sys/locations.rs +++ b/core/src/sys/locations.rs @@ -1,15 +1,21 @@ +use super::SysError; use crate::{ - file::{cas::FileIdentifierJob, indexer::IndexerJob}, + file::{ + cas::FileIdentifierJob, + indexer::{IndexerJob, IndexerJobInit}, + }, library::LibraryContext, node::LibraryNode, prisma::{file_path, location}, - ClientQuery, CoreEvent, LibraryQuery, + ClientQuery, CoreEvent, FileIdentifierJobInit, Job, LibraryQuery, ThumbnailJob, + ThumbnailJobInit, }; - use log::info; use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::path::{Path, PathBuf}; +use std::{ + fmt::Debug, + path::{Path, PathBuf}, +}; use thiserror::Error; use tokio::{ fs::{metadata, File}, @@ -18,14 +24,12 @@ use tokio::{ use ts_rs::TS; use uuid::Uuid; -use super::SysError; - #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] pub struct LocationResource { pub id: i32, pub name: Option, - pub path: Option, + pub path: Option, pub total_capacity: Option, pub available_capacity: Option, pub is_removable: Option, @@ -40,7 +44,7 @@ impl From for LocationResource { LocationResource { id: data.id, name: data.name, - path: data.local_path, + path: data.local_path.map(PathBuf::from), total_capacity: data.total_capacity, available_capacity: data.available_capacity, is_removable: data.is_removable, @@ -88,21 +92,31 @@ pub async fn get_location( pub async fn scan_location(ctx: &LibraryContext, location_id: i32, path: impl AsRef) { let path_buf = path.as_ref().to_path_buf(); - ctx.spawn_job(Box::new(IndexerJob { - path: path_buf.clone(), - })) + ctx.spawn_job(Job::new( + IndexerJobInit { + path: path_buf.clone(), + }, + Box::new(IndexerJob {}), + )) .await; - ctx.queue_job(Box::new(FileIdentifierJob { - location_id, - path: path_buf, - })) + ctx.queue_job(Job::new( + FileIdentifierJobInit { + location_id, + path: path_buf.clone(), + }, + Box::new(FileIdentifierJob {}), + )) + .await; + + ctx.queue_job(Job::new( + ThumbnailJobInit { + location_id, + path: path_buf, + background: true, + }, + Box::new(ThumbnailJob {}), + )) .await; - // TODO: make a way to stop jobs so this can be canceled without rebooting app - // ctx.queue_job(Box::new(ThumbnailJob { - // location_id, - // path: "".to_string(), - // background: false, - // })); } pub async fn new_location_and_scan( @@ -173,7 +187,7 @@ pub async fn create_location( .db .location() .create( - location::pub_id::set(uuid.to_string()), + location::pub_id::set(uuid.as_bytes().to_vec()), vec![ location::name::set(Some( path.file_name().unwrap().to_string_lossy().to_string(), @@ -231,7 +245,7 @@ pub async fn delete_location(ctx: &LibraryContext, location_id: i32) -> Result<( .await?; ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), + library_id: ctx.id, query: LibraryQuery::GetLocations, })) .await; diff --git a/core/src/sys/mod.rs b/core/src/sys/mod.rs index 8eafbf28b..af63fd920 100644 --- a/core/src/sys/mod.rs +++ b/core/src/sys/mod.rs @@ -6,7 +6,7 @@ pub use volumes::*; use thiserror::Error; -use crate::{job, prisma}; +use crate::prisma; #[derive(Error, Debug)] pub enum SysError { @@ -14,8 +14,6 @@ pub enum SysError { Location(#[from] LocationError), #[error("Error with system volumes")] Volume(String), - #[error("Error from job runner")] - Job(#[from] job::JobError), #[error("Database error")] Database(#[from] prisma::QueryError), } diff --git a/core/src/tag/mod.rs b/core/src/tag/mod.rs index 415c1780c..e6da9476b 100644 --- a/core/src/tag/mod.rs +++ b/core/src/tag/mod.rs @@ -11,12 +11,13 @@ use crate::{ use serde::{Deserialize, Serialize}; use thiserror::Error; use ts_rs::TS; +use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] pub struct Tag { pub id: i32, - pub pub_id: String, + pub pub_id: Uuid, pub name: Option, pub color: Option, @@ -43,7 +44,7 @@ impl From for Tag { fn from(data: tag::Data) -> Self { Self { id: data.id, - pub_id: data.pub_id, + pub_id: Uuid::from_slice(&data.pub_id).unwrap(), name: data.name, color: data.color, total_files: data.total_files, @@ -90,7 +91,7 @@ pub async fn create_tag( .db .tag() .create( - tag::pub_id::set(uuid::Uuid::new_v4().to_string()), + tag::pub_id::set(Uuid::new_v4().as_bytes().to_vec()), vec![tag::name::set(Some(name)), tag::color::set(Some(color))], ) .exec() @@ -98,7 +99,7 @@ pub async fn create_tag( .unwrap(); ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), + library_id: ctx.id, query: LibraryQuery::GetTags, })) .await; @@ -121,7 +122,7 @@ pub async fn update_tag( .unwrap(); ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id.to_string(), + library_id: ctx.id, query: LibraryQuery::GetTags, })) .await; @@ -153,7 +154,7 @@ pub async fn tag_delete(ctx: LibraryContext, id: i32) -> Result Result<()> { +async fn my_core_function(&ctx: CoreContext) -> Result<()> { let mut file = File::get_unique(1).await?; ctx.sync.operation(file.id, diff --git a/docs/product/roadmap.md b/docs/product/roadmap.md index 8233cf573..cd14aafb8 100644 --- a/docs/product/roadmap.md +++ b/docs/product/roadmap.md @@ -15,7 +15,7 @@ **To be developed (MVP):** - **Photos** - Photo and video albums similar to Apple/Google photos. -- **Search** - Deep search into your filesystem with a keybind, including offline locations. +- **Search** - Deep search into your filesystem with a keybinding, including offline locations. - **Tags** - Define routines on custom tags to automate workflows, easily tag files individually, in bulk and automatically via rules. - **Extensions** - Build tools on top of Spacedrive, extend functionality and integrate third party services. Extension directory on [spacedrive.com/extensions](/extensions). @@ -25,7 +25,7 @@ - **Cloud integration** - Index & backup to Apple Photos, Google Drive, Dropbox, OneDrive & Mega + easy API for the community to add more. - **Encrypted vault(s)** - Effortlessly manage & encrypt sensitive files, built on top of VeraCrypt. Encrypt individual files or create flexible-size vaults. - **Key manager** - View, mount, dismount and hide keys. Mounted keys automatically unlock respective areas of your filesystem. -- **Redundancy Goal** - Ensure a specific amount of copies exist for your important data, discover at-risk files and monitor device/drive health. +- **Redundancy Goal** - Ensure a specific amount of copies exists for your important data, discover at-risk files and monitor device/drive health. - **Timeline** - View a linear timeline of content, travel to any time and see media represented visually. - **Media encoder** - Encode video and audio into various formats, use Tags to automate. Built with FFMPEG. -- **Workers** - Utilize the compute power of your devices in unison to encode and perform tasks at increased speeds. +- **Workers** - Utilize the computing power of your devices in unison to encode and perform tasks at increased speeds. diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 000000000..4b4b62880 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,33 @@ +######################################################################################################################## +# Refer for explanation to following link: # +# https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md # +######################################################################################################################## + +pre-commit: + parallel: true + commands: + type-check: + glob: "*.{ts,tsx}" + run: pnpm typecheck + lint: + glob: '*.{ts,tsx}' + run: pnpm eslint {all_files} + spelling: + glob: '*.{ts,tsx,md,rs}' + run: pnpm cspell {staged_files} + markdown-link-check: + glob: '*.md' + run: pnpm markdown-link-check {staged_files} + rust-fmt: + glob: '*.rs' + run: cargo fmt --all -- --check + rust-lint-tauri: + run: cargo clippy --package spacedrive -- -D warnings + rust-lint-core: + run: cargo clippy --package sdcore --lib -- -D warnings + rust-lint-core-prisma: + run: cargo clippy --package prisma-cli -- -D warnings + rust-lint-core-derive: + run: cargo clippy --package core-derive --lib -- -D warnings + rust-lint-server: + run: cargo clippy --package server -- -D warnings diff --git a/package.json b/package.json index af5a13315..1124def65 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,20 @@ "typecheck": "pnpm -r exec tsc" }, "devDependencies": { + "@cspell/dict-rust": "^2.0.1", + "@cspell/dict-typescript": "^2.0.1", + "@evilmartians/lefthook": "^1.0.5", "@trivago/prettier-plugin-sort-imports": "^3.2.0", + "@typescript-eslint/eslint-plugin": "^5.30.7", + "@typescript-eslint/parser": "^5.30.7", + "cspell": "^6.4.0", + "eslint": "^8.20.0", + "eslint-config-prettier": "^8.5.0", + "eslint-config-standard-with-typescript": "^22.0.0", + "eslint-plugin-import": ">=2.25.2 <3.0.0", + "eslint-plugin-n": ">=15.0.0 <16.0.0", + "eslint-plugin-promise": ">=6.0.0 <7.0.0", + "markdown-link-check": "^3.10.2", "prettier": "^2.6.2", "turbo": "^1.2.14", "typescript": "^4.7.4" diff --git a/packages/interface/src/AppRouter.tsx b/packages/interface/src/AppRouter.tsx index 644864922..ccd9171a3 100644 --- a/packages/interface/src/AppRouter.tsx +++ b/packages/interface/src/AppRouter.tsx @@ -16,7 +16,7 @@ import { SettingsScreen } from './screens/settings/Settings'; import AppearanceSettings from './screens/settings/client/AppearanceSettings'; import ExtensionSettings from './screens/settings/client/ExtensionsSettings'; import GeneralSettings from './screens/settings/client/GeneralSettings'; -import KeybindSettings from './screens/settings/client/KeybindSettings'; +import KeybindingSettings from './screens/settings/client/KeybindingSettings'; import PrivacySettings from './screens/settings/client/PrivacySettings'; import AboutSpacedrive from './screens/settings/info/AboutSpacedrive'; import Changelog from './screens/settings/info/Changelog'; @@ -66,7 +66,7 @@ export function AppRouter() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/packages/interface/src/components/primitive/Checkbox.tsx b/packages/interface/src/components/primitive/Checkbox.tsx index d8c649a67..39ca5ccec 100644 --- a/packages/interface/src/components/primitive/Checkbox.tsx +++ b/packages/interface/src/components/primitive/Checkbox.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import React from 'react'; export interface CheckboxProps extends React.InputHTMLAttributes { - containerClasname?: string; + containerClassname?: string; } export const Checkbox: React.FC = (props) => { @@ -10,7 +10,7 @@ export const Checkbox: React.FC = (props) => {
- {/*
-
*/}
{props.locations.map((location, key) => ( { @@ -50,14 +47,7 @@ export default function FileItem(props: Props) {
) : ( -
+
(data: T) { return data; @@ -48,29 +44,31 @@ const GridItemContainer = styled.div` `; export const FileList: React.FC<{ location_id: number; path: string; limit: number }> = (props) => { - const path = props.path; const size = useWindowSize(); const tableContainer = useRef(null); const VList = useRef(null); - const { data: client } = useBridgeQuery('GetNode', undefined, { + const { data: client } = useBridgeQuery(['getNode'], { refetchOnWindowFocus: false }); const { selectedRowIndex, setSelectedRowIndex, setLocationId, layoutMode } = useExplorerStore(); const [goingUp, setGoingUp] = useState(false); - const { data: currentDir } = useLibraryQuery('GetExplorerDir', { - location_id: props.location_id, - path, - limit: props.limit - }); + const { data: currentDir } = useLibraryQuery([ + 'locations.getExplorerDir', + { + location_id: props.location_id, + path: props.path, + limit: props.limit + } + ]); useEffect(() => { if (selectedRowIndex === 0 && goingUp) { VList.current?.scrollTo({ top: 0, behavior: 'smooth' }); } - if (selectedRowIndex != -1 && typeof VList.current?.scrollIntoView === 'function') { + if (selectedRowIndex !== -1 && typeof VList.current?.scrollIntoView === 'function') { VList.current?.scrollIntoView({ index: goingUp ? selectedRowIndex - 1 : selectedRowIndex }); @@ -84,13 +82,14 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb useKey('ArrowUp', (e) => { e.preventDefault(); setGoingUp(true); - if (selectedRowIndex != -1 && selectedRowIndex !== 0) setSelectedRowIndex(selectedRowIndex - 1); + if (selectedRowIndex !== -1 && selectedRowIndex !== 0) + setSelectedRowIndex(selectedRowIndex - 1); }); useKey('ArrowDown', (e) => { e.preventDefault(); setGoingUp(false); - if (selectedRowIndex != -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1) + if (selectedRowIndex !== -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1) setSelectedRowIndex(selectedRowIndex + 1); }); @@ -168,17 +167,9 @@ interface RenderItemProps { } const RenderGridItem: React.FC = ({ item, index, dirId }) => { - // return
; const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore(); - const isActive = selectedRowIndex === index; - let [_, setSearchParams] = useSearchParams(); - function selectFileHandler() { - if (selectedRowIndex == index) setSelectedRowIndex(-1); - else setSelectedRowIndex(index); - } - return ( { @@ -187,8 +178,10 @@ const RenderGridItem: React.FC = ({ item, index, dirId }) => { } }} file={item} - selected={isActive} - onClick={selectFileHandler} + selected={selectedRowIndex === index} + onClick={() => { + setSelectedRowIndex(selectedRowIndex == index ? -1 : index); + }} /> ); }; @@ -196,18 +189,12 @@ const RenderGridItem: React.FC = ({ item, index, dirId }) => { const RenderRow: React.FC = ({ item, index, dirId }) => { const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore(); const isActive = selectedRowIndex === index; - let [_, setSearchParams] = useSearchParams(); - function selectFileHandler() { - if (selectedRowIndex == index) setSelectedRowIndex(-1); - else setSelectedRowIndex(index); - } - return useMemo( () => (
setSelectedRowIndex(selectedRowIndex == index ? -1 : index)} onDoubleClick={() => { if (item.is_dir) { setSearchParams({ path: item.materialized_path }); @@ -225,7 +212,7 @@ const RenderRow: React.FC = ({ item, index, dirId }) => { className="flex items-center px-4 py-2 pr-2 table-body-cell" style={{ width: col.width }} > - +
))}
@@ -235,26 +222,18 @@ const RenderRow: React.FC = ({ item, index, dirId }) => { }; const RenderCell: React.FC<{ - colKey?: ColumnKey; - dirId?: number; - file?: FilePath; + colKey: ColumnKey; + dirId: number; + file: FilePath; }> = ({ colKey, file, dirId }) => { const location = useContext(LocationContext); - if (!file || !colKey || !dirId) return <>; - - const row = file; - if (!row) return <>; - - const value = row[colKey]; - if (!value) return <>; - switch (colKey) { case 'name': return (
- +
{/* {colKey == 'name' && (() => { @@ -268,13 +247,13 @@ const RenderCell: React.FC<{ return ; } })()} */} - {row[colKey]} + {file[colKey]}
); // case 'size_in_bytes': // return {byteSize(Number(value || 0))}; case 'extension': - return {value}; + return {file[colKey]}; // case 'meta_integrity_hash': // return {value}; // case 'tags': diff --git a/packages/interface/src/components/file/FileThumb.tsx b/packages/interface/src/components/file/FileThumb.tsx index 62164a88d..162712245 100644 --- a/packages/interface/src/components/file/FileThumb.tsx +++ b/packages/interface/src/components/file/FileThumb.tsx @@ -1,5 +1,4 @@ -import { useBridgeQuery, useExplorerStore } from '@sd/client'; -import { AppPropsContext } from '@sd/client'; +import { AppPropsContext, useExplorerStore } from '@sd/client'; import { FilePath } from '@sd/core'; import clsx from 'clsx'; import React, { useContext } from 'react'; @@ -15,7 +14,7 @@ export default function FileThumb(props: { const appProps = useContext(AppPropsContext); const { newThumbnails } = useExplorerStore(); - const hasNewThumbnail = !!newThumbnails[props?.file?.file?.cas_id ?? '']; + const hasNewThumbnail = !!newThumbnails[props.file.file?.cas_id ?? '']; if (props.file.is_dir) { return ; diff --git a/packages/interface/src/components/file/Icon.tsx b/packages/interface/src/components/file/Icon.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/interface/src/components/file/Inspector.tsx b/packages/interface/src/components/file/Inspector.tsx index 42b2d127a..8d08d26ea 100644 --- a/packages/interface/src/components/file/Inspector.tsx +++ b/packages/interface/src/components/file/Inspector.tsx @@ -1,13 +1,12 @@ -import { Transition } from '@headlessui/react'; import { ShareIcon } from '@heroicons/react/solid'; -import { useInspectorStore, useLibraryCommand } from '@sd/client'; -import { FilePath, LocationResource } from '@sd/core'; +import { useLibraryMutation } from '@sd/client'; +import { FilePath, Location } from '@sd/core'; import { Button, TextArea } from '@sd/ui'; import moment from 'moment'; import { Heart, Link } from 'phosphor-react'; import React, { useEffect, useState } from 'react'; -import { default as types } from '../../constants/file-types.json'; +import types from '../../constants/file-types.json'; import FileThumb from './FileThumb'; interface MetaItemProps { @@ -32,26 +31,38 @@ const Divider = () =>
{!!file_path && ( @@ -110,7 +110,10 @@ export const Inspector = (props: { )} - + platform === 'macOS' ? classnames : ''; export const Sidebar: React.FC = (props) => { - const { isExperimental } = useNodeStore(); - const navigate = useNavigate(); - const appProps = useContext(AppPropsContext); - - const { data: locationsResponse, isError: isLocationsError } = useLibraryQuery('GetLocations'); - - let locations = Array.isArray(locationsResponse) ? locationsResponse : []; + const { data: locations } = useLibraryQuery(['locations.get']); // initialize libraries const { init: initLibraries, switchLibrary } = useLibraryStore(); - const { currentLibrary, libraries, currentLibraryUuid } = useCurrentLibrary(); useEffect(() => { if (libraries && !currentLibraryUuid) initLibraries(libraries); }, [libraries, currentLibraryUuid]); - const { mutate: createLocation } = useLibraryCommand('LocCreate'); + const { mutate: createLocation } = useLibraryMutation('locations.create'); - const { data: tags } = useLibraryQuery('GetTags'); + const { data: tags } = useLibraryQuery(['tags.get']); return (
= (props) => { */} + {/* */} { setOpenDeleteModal(false); } @@ -39,7 +33,7 @@ function LibraryListItem(props: { library: LibraryConfigWrapped }) { title="Delete Library" description="Deleting a library will permanently the database, the files themselves will not be deleted." ctaAction={() => { - deleteLib({ id: props.library.uuid }); + deleteLib(props.library.uuid); }} loading={libDeletePending} ctaDanger @@ -58,20 +52,15 @@ function LibraryListItem(props: { library: LibraryConfigWrapped }) { export default function LibrarySettings() { const [openCreateModal, setOpenCreateModal] = useState(false); const [newLibName, setNewLibName] = useState(''); - - const { mutate: createLibrary, isLoading: createLibLoading } = useBridgeCommand('CreateLibrary', { - onSuccess: () => { - setOpenCreateModal(false); + const { data: libraries } = useBridgeQuery(['library.get']); + const { mutate: createLibrary, isLoading: createLibLoading } = useBridgeMutation( + 'library.create', + { + onSuccess: () => { + setOpenCreateModal(false); + } } - }); - - const { data: libraries } = useBridgeQuery('GetLibraries'); - - function createNewLib() { - if (newLibName) { - createLibrary({ name: newLibName }); - } - } + ); return ( @@ -85,8 +74,9 @@ export default function LibrarySettings() { onOpenChange={setOpenCreateModal} title="Create New Library" description="Choose a name for your new library, you can configure this and more settings from the library settings later on." - ctaAction={createNewLib} + ctaAction={() => createLibrary(newLibName)} loading={createLibLoading} + submitDisabled={!newLibName} ctaLabel="Create" trigger={ -
From af76f455a2af4a911e79df7935dda357a7598f73 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 8 Aug 2022 12:22:05 +0800 Subject: [PATCH 34/51] fix landing page --- packages/ui/package.json | 2 ++ pnpm-lock.yaml | Bin 670909 -> 671549 bytes 2 files changed, 2 insertions(+) diff --git a/packages/ui/package.json b/packages/ui/package.json index 43ee7d7e4..813c90201 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -40,8 +40,10 @@ "@storybook/preset-scss": "^1.0.3", "@storybook/react": "^6.5.9", "@storybook/testing-library": "^0.0.13", + "@tailwindcss/typography": "^0.5.4", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", + "autoprefixer": "^10.4.7", "babel-loader": "^8.2.5", "css-loader": "^6.7.1", "postcss-loader": "^7.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4072f50af866e40f3500bc4708ac2b12078d9a5..ed6ef7a0e16933d7afb4ebd8de1704e578bc1a1e 100644 GIT binary patch delta 228 zcmdn{Q)BNxjSaK;Coh-gn=GZuH`zf|bn^!OO>95`hsm0@!kfFre#%c4hk%@@qpvI3Pzh#Kgb>Y2osBxdH6XXd3O7Z*Di>lsc~R1ur3 zFKh(VqcuGvmr11gtWEn_8^-NtZJ0O@POtmT#5MVrD3_Q%R^8JTXR&B(-}{G&gK_dS zL&@piFSGG&zxKKKmUwh0f+`i~3GZ3=?F)I+W0Wmueb8KJqlv6q!00RzI AHvj+t delta 107 zcmdn{Ph;;-jSaK;H*e=Z&bE1ln3BTe9AmD@@$4p(m)Y`djxt}%I{iTf6YKN~q0C~_ zH+VDiO%~v1n_M8w+q}%SeVHxe_GPwAvIn=iGjDy~&i#TJh*^M`6^Pk@m>q~YwsXJW HGztd*4Uj2d From c35856e242dfe2afb8e028724555c7f54431f770 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 8 Aug 2022 15:22:54 +0800 Subject: [PATCH 35/51] clearer documentation about Windows setup script --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1940cb05..c914f0127 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,7 @@ This project uses [Cargo](https://doc.rust-lang.org/cargo/getting-started/instal - This will install FFMPEG and any other required dependencies for Spacedrive to build. - For Windows users run using PowerShell: `.\.github\scripts\setup-system.ps1` - This will install pnpm, LLVM, FFMPEG and any other required dependencies for Spacedrive to build. + - Ensure you run it like documented above as it expects it is executed from the root of the repository. - `$ pnpm i` - `$ pnpm prep` - Runs all necessary codegen & builds required dependencies. From 8a79b072939899c181ee995cc51965c8cc655220 Mon Sep 17 00:00:00 2001 From: Utku <74243531+utkubakir@users.noreply.github.com> Date: Tue, 9 Aug 2022 06:29:14 +0300 Subject: [PATCH 36/51] Mobile app (#352) * Mobile app: Initial Commit! * Fix monorepo issues and add tailwind. Fix & Test tailwind. working ball app Fix workspace module import * Mobile app cleanup and structure * App Icons, Splash screen and eslint config * More cleanup * Use SVGs directly in React Native * Add placeholder files for project structrate * Cleanup all svg icons and modify metro config to use icons from interface package (temporary) * Potentially fix types for react & update some packages. * Onboarding screen + add reanimated & moti * Finishing touches on Onboarding screen * Persist onboarding, Button comp and Nav. flow. * Add mobile info to Contributing.md * Add .prettierignore for disabling auto import ordering for some files. * Introducing the new navigation flow * Change package name * use the new @sd/assets package * Add a temp. folder for @sd/assets organization * Drawer nav bar looks good now. * Hacky method to get the active route on drawer * Fix react native types & update few packages. * Drawer animation * Add counter * Collapsible Tags / Locations * rename Counter and add saveState prop * Sync counter with desktop version * Move some screens to BottomTab Nav. * Add Overview Stats * [WIP] - Device component * Upgrade to Expo 46 and fix types. * Add @sd/core to mobile * Fix eslint stuff * placeholder and some notes * Show folder icons on overview screen * Fixed android build, style and some screen tweaks * Add bottom sheet package * Fix bundler issues and rename landing package.json * Rename landing package.json * update all packages to latest React * Eject expo app * fix pnpm & expo & monorepo * monorepo debugging * cleanup dependencies & static link to shared packages * cleanup, switch to hermes, pollyfill intl for ios * Cleanup monorepo * Fix: Style for FileItem * Above average app icons * cleanup ios * update msrv * update codeowners for mobile * fix typecheck * update lockfile * fix ffmpeg install * rename UI to 'SpacedriveInterface' for clarity * Update codeowners * Fix eslint config mobile. * Refactor navigation flow, move types to navigators Co-authored-by: Utku <74243531+utkubkr@users.noreply.github.com> Co-authored-by: Oscar Beaumont Co-authored-by: Brendan Allan --- .cspell/frontend_custom_words.txt | 4 +- .eslintrc.js | 1 + .github/CODEOWNERS | 7 +- .github/scripts/setup-system.sh | 8 +- .prettierignore | 0 CONTRIBUTING.md | 12 +- apps/mobile/.buckconfig | 6 + apps/mobile/.eslintignore | 4 + apps/mobile/.eslintrc.json | 44 ++ apps/mobile/.gitattributes | 1 + apps/mobile/.gitignore | 55 ++ apps/mobile/.npmrc | 3 + apps/mobile/README.md | 1 + apps/mobile/android/.gitignore | 21 + apps/mobile/android/app/BUCK | 55 ++ apps/mobile/android/app/build.gradle | 376 ++++++++++++ apps/mobile/android/app/build_defs.bzl | 19 + apps/mobile/android/app/debug.keystore | Bin 0 -> 2257 bytes apps/mobile/android/app/proguard-rules.pro | 14 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../spacedrive/app/ReactNativeFlipper.java | 69 +++ .../android/app/src/main/AndroidManifest.xml | 35 ++ .../java/com/spacedrive/app/MainActivity.java | 83 +++ .../com/spacedrive/app/MainApplication.java | 106 ++++ .../MainApplicationReactNativeHost.java | 117 ++++ .../components/MainComponentsRegistry.java | 36 ++ ...ApplicationTurboModuleManagerDelegate.java | 48 ++ .../android/app/src/main/jni/Android.mk | 48 ++ .../jni/MainApplicationModuleProvider.cpp | 24 + .../main/jni/MainApplicationModuleProvider.h | 16 + ...nApplicationTurboModuleManagerDelegate.cpp | 45 ++ ...ainApplicationTurboModuleManagerDelegate.h | 38 ++ .../src/main/jni/MainComponentsRegistry.cpp | 61 ++ .../app/src/main/jni/MainComponentsRegistry.h | 32 + .../android/app/src/main/jni/OnLoad.cpp | 11 + .../res/drawable-hdpi/splashscreen_image.png | Bin 0 -> 79413 bytes .../res/drawable-mdpi/splashscreen_image.png | Bin 0 -> 79413 bytes .../res/drawable-xhdpi/splashscreen_image.png | Bin 0 -> 79413 bytes .../drawable-xxhdpi/splashscreen_image.png | Bin 0 -> 79413 bytes .../drawable-xxxhdpi/splashscreen_image.png | Bin 0 -> 79413 bytes .../res/drawable/rn_edit_text_material.xml | 36 ++ .../src/main/res/drawable/splashscreen.xml | 3 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4303 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 4303 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5059 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2297 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 2297 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2756 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6634 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 6634 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7676 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 12317 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 12317 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 13996 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 18953 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 18953 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 21326 bytes .../app/src/main/res/values-night/colors.xml | 1 + .../app/src/main/res/values/colors.xml | 6 + .../app/src/main/res/values/strings.xml | 6 + .../app/src/main/res/values/styles.xml | 17 + apps/mobile/android/build.gradle | 59 ++ apps/mobile/android/gradle.properties | 53 ++ .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + apps/mobile/android/gradlew | 234 +++++++ apps/mobile/android/gradlew.bat | 89 +++ apps/mobile/android/settings.gradle | 17 + apps/mobile/app.json | 24 + apps/mobile/babel.config.js | 7 + apps/mobile/index.js | 8 + apps/mobile/ios/.gitignore | 30 + apps/mobile/ios/.xcode.env | 1 + apps/mobile/ios/Podfile | 49 ++ apps/mobile/ios/Podfile.lock | 579 ++++++++++++++++++ apps/mobile/ios/Podfile.properties.json | 3 + .../ios/Spacedrive.xcodeproj/project.pbxproj | 512 ++++++++++++++++ .../xcschemes/Spacedrive.xcscheme | 97 +++ apps/mobile/ios/Spacedrive/AppDelegate.h | 9 + apps/mobile/ios/Spacedrive/AppDelegate.mm | 166 +++++ .../AppIcon.appiconset/App-Icon-20x20@1x.png | Bin 0 -> 733 bytes .../AppIcon.appiconset/App-Icon-20x20@2x.png | Bin 0 -> 1949 bytes .../AppIcon.appiconset/App-Icon-20x20@3x.png | Bin 0 -> 3645 bytes .../AppIcon.appiconset/App-Icon-29x29@1x.png | Bin 0 -> 1233 bytes .../AppIcon.appiconset/App-Icon-29x29@2x.png | Bin 0 -> 3471 bytes .../AppIcon.appiconset/App-Icon-29x29@3x.png | Bin 0 -> 6389 bytes .../AppIcon.appiconset/App-Icon-40x40@1x.png | Bin 0 -> 1949 bytes .../AppIcon.appiconset/App-Icon-40x40@2x.png | Bin 0 -> 5587 bytes .../AppIcon.appiconset/App-Icon-40x40@3x.png | Bin 0 -> 10559 bytes .../AppIcon.appiconset/App-Icon-60x60@2x.png | Bin 0 -> 10559 bytes .../AppIcon.appiconset/App-Icon-60x60@3x.png | Bin 0 -> 19556 bytes .../AppIcon.appiconset/App-Icon-76x76@1x.png | Bin 0 -> 5181 bytes .../AppIcon.appiconset/App-Icon-76x76@2x.png | Bin 0 -> 14875 bytes .../App-Icon-83.5x83.5@2x.png | Bin 0 -> 17403 bytes .../AppIcon.appiconset/Contents.json | 122 ++++ .../AppIcon.appiconset/ItunesArtwork@2x.png | Bin 0 -> 379434 bytes .../Spacedrive/Images.xcassets/Contents.json | 6 + .../SplashScreen.imageset/Contents.json | 21 + .../SplashScreen.imageset/image.png | Bin 0 -> 79413 bytes .../Contents.json | 21 + .../SplashScreenBackground.imageset/image.png | Bin 0 -> 68 bytes apps/mobile/ios/Spacedrive/Info.plist | 77 +++ .../ios/Spacedrive/Spacedrive.entitlements | 8 + .../ios/Spacedrive/SplashScreen.storyboard | 51 ++ .../ios/Spacedrive/Supporting/Expo.plist | 16 + apps/mobile/ios/Spacedrive/main.m | 10 + .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + apps/mobile/metro.config.js | 64 ++ apps/mobile/package.json | 56 +- apps/mobile/pnpm-lock.yaml | Bin 0 -> 304815 bytes apps/mobile/pnpm-workspace.yaml | 0 apps/mobile/src/App.tsx | 54 ++ apps/mobile/src/assets/icons/file/index.ts | 347 +++++++++++ apps/mobile/src/assets/temp/README.md | 3 + apps/mobile/src/assets/temp/folder-white.svg | 15 + apps/mobile/src/assets/temp/folder.svg | 16 + apps/mobile/src/assets/temp/logo.png | Bin 0 -> 129137 bytes .../src/components/animation/layout.tsx | 101 +++ apps/mobile/src/components/base/Button.tsx | 58 ++ .../components/browse/BrowseLocationItem.tsx | 26 + .../src/components/browse/BrowseTagItem.tsx | 26 + apps/mobile/src/components/device/Device.tsx | 309 ++++++++++ .../src/components/drawer/DrawerContent.tsx | 64 ++ .../src/components/drawer/DrawerItem.tsx | 34 + .../components/drawer/DrawerScreenWrapper.tsx | 31 + apps/mobile/src/components/file/FileItem.tsx | 77 +++ apps/mobile/src/components/icons/Folder.tsx | 27 + apps/mobile/src/components/layout/Card.tsx | 0 .../src/components/layout/CollapsibleView.tsx | 46 ++ .../layout/VirtualizedListWrapper.tsx | 15 + .../src/components/modals/FileDetails.tsx | 1 + apps/mobile/src/constants/Layout.ts | 11 + apps/mobile/src/containers/OverviewStats.tsx | 50 ++ apps/mobile/src/hooks/useCachedResources.ts | 47 ++ apps/mobile/src/hooks/useCounter.ts | 71 +++ apps/mobile/src/lib/storage.ts | 71 +++ apps/mobile/src/lib/tailwind.js | 5 + .../mobile/src/navigation/DrawerNavigator.tsx | 48 ++ .../src/navigation/LinkingConfiguration.ts | 27 + .../src/navigation/OnboardingNavigator.tsx | 20 + apps/mobile/src/navigation/TabNavigator.tsx | 84 +++ apps/mobile/src/navigation/index.tsx | 34 + .../src/navigation/tabs/BrowseStack.tsx | 30 + apps/mobile/src/screens/Browse.tsx | 78 +++ apps/mobile/src/screens/Location.tsx | 13 + apps/mobile/src/screens/NotFound.tsx | 15 + apps/mobile/src/screens/Overview.tsx | 78 +++ apps/mobile/src/screens/Photos.tsx | 13 + apps/mobile/src/screens/Spaces.tsx | 14 + apps/mobile/src/screens/Tag.tsx | 13 + .../src/screens/modals/settings/Settings.tsx | 14 + .../src/screens/onboarding/Onboarding.tsx | 59 ++ apps/mobile/src/stores/useOnboardingStore.ts | 11 + apps/mobile/src/types/declarations.d.ts | 13 + apps/mobile/src/types/helper.ts | 1 + apps/mobile/tailwind.config.js | 63 ++ apps/mobile/tsconfig.json | 4 + package.json | 2 - packages/interface/src/App.tsx | 2 +- packages/interface/src/index.ts | 4 +- packages/ui/package.json | 1 + pnpm-lock.yaml | Bin 671549 -> 668113 bytes pnpm-workspace.yaml | 3 +- tsconfig.json | 3 + 167 files changed, 6033 insertions(+), 17 deletions(-) create mode 100644 .prettierignore create mode 100644 apps/mobile/.buckconfig create mode 100644 apps/mobile/.eslintignore create mode 100755 apps/mobile/.eslintrc.json create mode 100644 apps/mobile/.gitattributes create mode 100644 apps/mobile/.gitignore create mode 100644 apps/mobile/.npmrc create mode 100644 apps/mobile/README.md create mode 100644 apps/mobile/android/.gitignore create mode 100644 apps/mobile/android/app/BUCK create mode 100644 apps/mobile/android/app/build.gradle create mode 100644 apps/mobile/android/app/build_defs.bzl create mode 100644 apps/mobile/android/app/debug.keystore create mode 100644 apps/mobile/android/app/proguard-rules.pro create mode 100644 apps/mobile/android/app/src/debug/AndroidManifest.xml create mode 100644 apps/mobile/android/app/src/debug/java/com/spacedrive/app/ReactNativeFlipper.java create mode 100644 apps/mobile/android/app/src/main/AndroidManifest.xml create mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/MainActivity.java create mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java create mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/MainApplicationReactNativeHost.java create mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/components/MainComponentsRegistry.java create mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java create mode 100644 apps/mobile/android/app/src/main/jni/Android.mk create mode 100644 apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.cpp create mode 100644 apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.h create mode 100644 apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp create mode 100644 apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h create mode 100644 apps/mobile/android/app/src/main/jni/MainComponentsRegistry.cpp create mode 100644 apps/mobile/android/app/src/main/jni/MainComponentsRegistry.h create mode 100644 apps/mobile/android/app/src/main/jni/OnLoad.cpp create mode 100644 apps/mobile/android/app/src/main/res/drawable-hdpi/splashscreen_image.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-mdpi/splashscreen_image.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png create mode 100644 apps/mobile/android/app/src/main/res/drawable/rn_edit_text_material.xml create mode 100644 apps/mobile/android/app/src/main/res/drawable/splashscreen.xml create mode 100644 apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/values-night/colors.xml create mode 100644 apps/mobile/android/app/src/main/res/values/colors.xml create mode 100644 apps/mobile/android/app/src/main/res/values/strings.xml create mode 100644 apps/mobile/android/app/src/main/res/values/styles.xml create mode 100644 apps/mobile/android/build.gradle create mode 100644 apps/mobile/android/gradle.properties create mode 100644 apps/mobile/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 apps/mobile/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 apps/mobile/android/gradlew create mode 100644 apps/mobile/android/gradlew.bat create mode 100644 apps/mobile/android/settings.gradle create mode 100644 apps/mobile/app.json create mode 100644 apps/mobile/babel.config.js create mode 100644 apps/mobile/index.js create mode 100644 apps/mobile/ios/.gitignore create mode 100644 apps/mobile/ios/.xcode.env create mode 100644 apps/mobile/ios/Podfile create mode 100644 apps/mobile/ios/Podfile.lock create mode 100644 apps/mobile/ios/Podfile.properties.json create mode 100644 apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj create mode 100644 apps/mobile/ios/Spacedrive.xcodeproj/xcshareddata/xcschemes/Spacedrive.xcscheme create mode 100644 apps/mobile/ios/Spacedrive/AppDelegate.h create mode 100644 apps/mobile/ios/Spacedrive/AppDelegate.mm create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/Contents.json create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreen.imageset/Contents.json create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreen.imageset/image.png create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreenBackground.imageset/Contents.json create mode 100644 apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreenBackground.imageset/image.png create mode 100644 apps/mobile/ios/Spacedrive/Info.plist create mode 100644 apps/mobile/ios/Spacedrive/Spacedrive.entitlements create mode 100644 apps/mobile/ios/Spacedrive/SplashScreen.storyboard create mode 100644 apps/mobile/ios/Spacedrive/Supporting/Expo.plist create mode 100644 apps/mobile/ios/Spacedrive/main.m create mode 100644 apps/mobile/ios/spacedrive.xcworkspace/contents.xcworkspacedata create mode 100644 apps/mobile/ios/spacedrive.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 apps/mobile/metro.config.js create mode 100644 apps/mobile/pnpm-lock.yaml create mode 100644 apps/mobile/pnpm-workspace.yaml create mode 100644 apps/mobile/src/App.tsx create mode 100644 apps/mobile/src/assets/icons/file/index.ts create mode 100644 apps/mobile/src/assets/temp/README.md create mode 100644 apps/mobile/src/assets/temp/folder-white.svg create mode 100644 apps/mobile/src/assets/temp/folder.svg create mode 100644 apps/mobile/src/assets/temp/logo.png create mode 100644 apps/mobile/src/components/animation/layout.tsx create mode 100644 apps/mobile/src/components/base/Button.tsx create mode 100644 apps/mobile/src/components/browse/BrowseLocationItem.tsx create mode 100644 apps/mobile/src/components/browse/BrowseTagItem.tsx create mode 100644 apps/mobile/src/components/device/Device.tsx create mode 100644 apps/mobile/src/components/drawer/DrawerContent.tsx create mode 100644 apps/mobile/src/components/drawer/DrawerItem.tsx create mode 100644 apps/mobile/src/components/drawer/DrawerScreenWrapper.tsx create mode 100644 apps/mobile/src/components/file/FileItem.tsx create mode 100644 apps/mobile/src/components/icons/Folder.tsx create mode 100644 apps/mobile/src/components/layout/Card.tsx create mode 100644 apps/mobile/src/components/layout/CollapsibleView.tsx create mode 100644 apps/mobile/src/components/layout/VirtualizedListWrapper.tsx create mode 100644 apps/mobile/src/components/modals/FileDetails.tsx create mode 100644 apps/mobile/src/constants/Layout.ts create mode 100644 apps/mobile/src/containers/OverviewStats.tsx create mode 100644 apps/mobile/src/hooks/useCachedResources.ts create mode 100644 apps/mobile/src/hooks/useCounter.ts create mode 100644 apps/mobile/src/lib/storage.ts create mode 100644 apps/mobile/src/lib/tailwind.js create mode 100644 apps/mobile/src/navigation/DrawerNavigator.tsx create mode 100644 apps/mobile/src/navigation/LinkingConfiguration.ts create mode 100644 apps/mobile/src/navigation/OnboardingNavigator.tsx create mode 100644 apps/mobile/src/navigation/TabNavigator.tsx create mode 100644 apps/mobile/src/navigation/index.tsx create mode 100644 apps/mobile/src/navigation/tabs/BrowseStack.tsx create mode 100644 apps/mobile/src/screens/Browse.tsx create mode 100644 apps/mobile/src/screens/Location.tsx create mode 100644 apps/mobile/src/screens/NotFound.tsx create mode 100644 apps/mobile/src/screens/Overview.tsx create mode 100644 apps/mobile/src/screens/Photos.tsx create mode 100644 apps/mobile/src/screens/Spaces.tsx create mode 100644 apps/mobile/src/screens/Tag.tsx create mode 100644 apps/mobile/src/screens/modals/settings/Settings.tsx create mode 100644 apps/mobile/src/screens/onboarding/Onboarding.tsx create mode 100644 apps/mobile/src/stores/useOnboardingStore.ts create mode 100644 apps/mobile/src/types/declarations.d.ts create mode 100644 apps/mobile/src/types/helper.ts create mode 100644 apps/mobile/tailwind.config.js create mode 100644 apps/mobile/tsconfig.json diff --git a/.cspell/frontend_custom_words.txt b/.cspell/frontend_custom_words.txt index 34fcd1c69..5506b5162 100644 --- a/.cspell/frontend_custom_words.txt +++ b/.cspell/frontend_custom_words.txt @@ -79,4 +79,6 @@ lacie classname wunsub immer -tada \ No newline at end of file +tada +moti +pressable \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 0945e58c7..16fa89674 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,6 +6,7 @@ module.exports = { 'apps/desktop/tsconfig.json', 'apps/web/tsconfig.json', 'apps/landing/tsconfig.json', + 'apps/mobile/tsconfig.json', 'packages/client/tsconfig.json', 'packages/interface/tsconfig.json', 'packages/ui/tsconfig.json' diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ebfdf771d..bb30b4082 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,9 +8,11 @@ # frontend apps (Rust bridges and tech functionality -- no real visual implications) /apps/desktop/ @jamiepine @Brendonovich @oscartbeaumont -/apps/mobile/ @jamiepine @Brendonovich @oscartbeaumont /apps/web/ @jamiepine @maxichrome +# mobile +/apps/mobile/ @jamiepine @Brendonovich @oscartbeaumont @utkubakir + # core logic /core/ @jamiepine @Brendonovich @oscartbeaumont /packages/macos/ @jamiepine @Brendonovich @oscartbeaumont @@ -22,8 +24,9 @@ /apps/landing/ @jamiepine @maxichrome # UI -/packages/interface/ @jamiepine @maxichrome +/packages/interface/ @jamiepine @maxichrome @utkubakir /packages/ui/ @jamiepine @maxichrome +/packages/assets/ @jamiepine @utkubakir # base config files /* @jamiepine diff --git a/.github/scripts/setup-system.sh b/.github/scripts/setup-system.sh index 81f100c96..83c90ae1b 100755 --- a/.github/scripts/setup-system.sh +++ b/.github/scripts/setup-system.sh @@ -58,12 +58,12 @@ if [[ "$OSTYPE" == "linux-gnu"* ]]; then echo "Your machine has been setup for Spacedrive development!" elif [[ "$OSTYPE" == "darwin"* ]]; then - if !brew tap spacedrive/deps &> /dev/null; then - brew tap-new spacedrive/deps 2&> /dev/null + if ! brew tap | grep spacedriveapp/deps > /dev/null; then + brew tap-new spacedriveapp/deps > /dev/null fi - brew extract --force --version 5.0.1 ffmpeg spacedrive/deps + brew extract --force --version 5.0.1 ffmpeg spacedriveapp/deps > /dev/null brew unlink ffmpeg &> /dev/null || true - brew install spacedrive/deps/ffmpeg@5.0.1 &> /dev/null + brew install spacedriveapp/deps/ffmpeg@5.0.1 &> /dev/null echo "ffmpeg v5.0.1 has been installed and is now being used on your system." else diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..e69de29bb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c914f0127..ee1475ee3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,9 +56,18 @@ To run the landing page - `$ pnpm web dev` - runs the web app for the embed - `$ pnpm landing dev` +To run mobile app + +- `$ cd apps/mobile && pnpm i` - As this is a seperated workspace, you need to do this! +- `$ pnpm android` - runs on Android Emulator +- `$ pnpm ios` - runs on iOS Emulator +- `$ pnpm dev` - For already bundled app + +You also need `expo-cli` installed globally. + If you are having issues ensure you are using the following versions of Rust and Node: -- Rust version: **1.60.0** +- Rust version: **1.62.0** - Node version: **17** ### Pull Request @@ -79,7 +88,6 @@ Congratulations :tada::tada: The Spacedrive team thanks you :sparkles:. Once your PR is merged, your contributions will be included in the next release of the application. - ### Common Errors #### `xcrun: error: unable to find utility "xctest", not a developer tool or in PATH` diff --git a/apps/mobile/.buckconfig b/apps/mobile/.buckconfig new file mode 100644 index 000000000..934256cb2 --- /dev/null +++ b/apps/mobile/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/apps/mobile/.eslintignore b/apps/mobile/.eslintignore new file mode 100644 index 000000000..b85fcb7dd --- /dev/null +++ b/apps/mobile/.eslintignore @@ -0,0 +1,4 @@ +node_modules/ +android/ +ios/ +.expo \ No newline at end of file diff --git a/apps/mobile/.eslintrc.json b/apps/mobile/.eslintrc.json new file mode 100755 index 000000000..3c3fc8145 --- /dev/null +++ b/apps/mobile/.eslintrc.json @@ -0,0 +1,44 @@ +{ + "env": { + "react-native/react-native": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": ["react", "react-native", "react-hooks", "@typescript-eslint"], + "rules": { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "react/display-name": "off", + "react/prop-types": "off", + "no-control-regex": "off", + "no-mixed-spaces-and-tabs": ["warn", "smart-tabs"], + "react/react-in-jsx-scope": "off", + // Aggressively disable some rules for React Native. + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/consistent-type-definitions": "off", + "@typescript-eslint/no-floating-promises": "off" + }, + "settings": { + "react": { + "version": "detect" // "detect" automatically picks the version you have installed. + } + } +} diff --git a/apps/mobile/.gitattributes b/apps/mobile/.gitattributes new file mode 100644 index 000000000..d42ff1835 --- /dev/null +++ b/apps/mobile/.gitattributes @@ -0,0 +1 @@ +*.pbxproj -text diff --git a/apps/mobile/.gitignore b/apps/mobile/.gitignore new file mode 100644 index 000000000..c8eb0f9a6 --- /dev/null +++ b/apps/mobile/.gitignore @@ -0,0 +1,55 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore +!debug.keystore + +# Bundle artifacts +*.jsbundle + +# CocoaPods +/ios/Pods/ + +# Expo +.expo/ +web-build/ +dist/ diff --git a/apps/mobile/.npmrc b/apps/mobile/.npmrc new file mode 100644 index 000000000..a8fd4724d --- /dev/null +++ b/apps/mobile/.npmrc @@ -0,0 +1,3 @@ +strict-peer-dependencies = false +ignore-workspace-root-check = true +shamefully-hoist = true diff --git a/apps/mobile/README.md b/apps/mobile/README.md new file mode 100644 index 000000000..105fc3296 --- /dev/null +++ b/apps/mobile/README.md @@ -0,0 +1 @@ +Make sure to run `pnpm i` in this folder after making changes to the `packages`. diff --git a/apps/mobile/android/.gitignore b/apps/mobile/android/.gitignore new file mode 100644 index 000000000..64436baaf --- /dev/null +++ b/apps/mobile/android/.gitignore @@ -0,0 +1,21 @@ +# OSX +# +.DS_Store + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof + +# BUCK +buck-out/ +\.buckd/ +*.keystore +!debug.keystore + +# Bundle artifacts +*.jsbundle diff --git a/apps/mobile/android/app/BUCK b/apps/mobile/android/app/BUCK new file mode 100644 index 000000000..cabced2a6 --- /dev/null +++ b/apps/mobile/android/app/BUCK @@ -0,0 +1,55 @@ +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") + +lib_deps = [] + +create_aar_targets(glob(["libs/*.aar"])) + +create_jar_targets(glob(["libs/*.jar"])) + +android_library( + name = "all-libs", + exported_deps = lib_deps, +) + +android_library( + name = "app-code", + srcs = glob([ + "src/main/java/**/*.java", + ]), + deps = [ + ":all-libs", + ":build_config", + ":res", + ], +) + +android_build_config( + name = "build_config", + package = "com.spacedrive.app", +) + +android_resource( + name = "res", + package = "com.spacedrive.app", + res = "src/main/res", +) + +android_binary( + name = "app", + keystore = "//android/keystores:debug", + manifest = "src/main/AndroidManifest.xml", + package_type = "debug", + deps = [ + ":app-code", + ], +) diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle new file mode 100644 index 000000000..ff86ce652 --- /dev/null +++ b/apps/mobile/android/app/build.gradle @@ -0,0 +1,376 @@ +apply plugin: "com.android.application" + +import com.android.build.OutputFile +import org.apache.tools.ant.taskdefs.condition.Os + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "../../node_modules/react-native/react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation. If none specified and + * // "index.android.js" exists, it will be used. Otherwise "index.js" is + * // default. Can be overridden with ENTRY_FILE environment variable. + * entryFile: "index.android.js", + * + * // https://reactnative.dev/docs/performance#enable-the-ram-format + * bundleCommand: "ram-bundle", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // whether to disable dev mode in custom build variants (by default only disabled in release) + * // for example: to disable dev mode in the staging build type (if configured) + * devDisabledInStaging: true, + * // The configuration property can be in the following formats + * // 'devDisabledIn${productFlavor}${buildType}' + * // 'devDisabledIn${buildType}' + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // date; if you have any other folders that you want to ignore for performance reasons (gradle + * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ + * // for example, you might want to remove it from here. + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"], + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] + * ] + */ + +def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() + +def reactNativeRoot = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + +project.ext.react = [ + entryFile: ["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android"].execute(null, rootDir).text.trim(), + enableHermes: (findProperty('expo.jsEngine') ?: "jsc") == "hermes", + hermesCommand: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc", + cliPath: "${reactNativeRoot}/cli.js", + composeSourceMapsPath: "${reactNativeRoot}/scripts/compose-source-maps.js", +] + +apply from: new File(reactNativeRoot, "react.gradle") + +/** + * Set this to true to create two separate APKs instead of one: + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() + +/** + * The preferred build flavor of JavaScriptCore. + * + * For example, to use the international variant, you can use: + * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'org.webkit:android-jsc:+' + +/** + * Whether to enable the Hermes VM. + * + * This should be set on project.ext.react and that value will be read here. If it is not set + * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode + * and the benefits of using Hermes will therefore be sharply reduced. + */ +def enableHermes = project.ext.react.get("enableHermes", false); + +/** + * Architectures to build native code for. + */ +def reactNativeArchitectures() { + def value = project.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} + +android { + ndkVersion rootProject.ext.ndkVersion + + compileSdkVersion rootProject.ext.compileSdkVersion + + defaultConfig { + applicationId 'com.spacedrive.app' + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "0.0.1" + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + + if (isNewArchitectureEnabled()) { + // We configure the NDK build only if you decide to opt-in for the New Architecture. + externalNativeBuild { + ndkBuild { + arguments "APP_PLATFORM=android-21", + "APP_STL=c++_shared", + "NDK_TOOLCHAIN_VERSION=clang", + "GENERATED_SRC_DIR=$buildDir/generated/source", + "PROJECT_BUILD_DIR=$buildDir", + "REACT_ANDROID_DIR=${reactNativeRoot}/ReactAndroid", + "REACT_ANDROID_BUILD_DIR=${reactNativeRoot}/ReactAndroid/build", + "NODE_MODULES_DIR=$rootDir/../node_modules" + cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1" + cppFlags "-std=c++17" + // Make sure this target name is the same you specify inside the + // src/main/jni/Android.mk file for the `LOCAL_MODULE` variable. + targets "spacedrive_appmodules" + + // Fix for windows limit on number of character in file paths and in command lines + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + arguments "NDK_APP_SHORT_COMMANDS=true" + } + } + } + if (!enableSeparateBuildPerCPUArchitecture) { + ndk { + abiFilters (*reactNativeArchitectures()) + } + } + } + } + + if (isNewArchitectureEnabled()) { + // We configure the NDK build only if you decide to opt-in for the New Architecture. + externalNativeBuild { + ndkBuild { + path "$projectDir/src/main/jni/Android.mk" + } + } + def reactAndroidProjectDir = project(':ReactAndroid').projectDir + def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") + into("$buildDir/react-ndk/exported") + } + def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") + into("$buildDir/react-ndk/exported") + } + afterEvaluate { + // If you wish to add a custom TurboModule or component locally, + // you should uncomment this line. + // preBuild.dependsOn("generateCodegenArtifactsFromSchema") + preDebugBuild.dependsOn(packageReactNdkDebugLibs) + preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) + + // Due to a bug inside AGP, we have to explicitly set a dependency + // between configureNdkBuild* tasks and the preBuild tasks. + // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 + configureNdkBuildRelease.dependsOn(preReleaseBuild) + configureNdkBuildDebug.dependsOn(preDebugBuild) + reactNativeArchitectures().each { architecture -> + tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure { + dependsOn("preDebugBuild") + } + tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure { + dependsOn("preReleaseBuild") + } + } + } + } + + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include (*reactNativeArchitectures()) + } + } + signingConfigs { + debug { + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + // Caution! In production, you need to generate your own keystore file. + // see https://reactnative.dev/docs/signed-apk-android. + signingConfig signingConfigs.debug + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // https://developer.android.com/studio/build/configure-apk-splits.html + def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + + } + } +} + +// Apply static values from `gradle.properties` to the `android.packagingOptions` +// Accepts values in comma delimited lists, example: +// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini +["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> + // Split option: 'foo,bar' -> ['foo', 'bar'] + def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); + // Trim all elements in place. + for (i in 0.. 0) { + println "android.packagingOptions.$prop += $options ($options.length)" + // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' + options.each { + android.packagingOptions[prop] += it + } + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" // From node_modules + + def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; + def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; + def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; + def frescoVersion = rootProject.ext.frescoVersion + + // If your app supports Android versions before Ice Cream Sandwich (API level 14) + if (isGifEnabled || isWebpEnabled) { + implementation "com.facebook.fresco:fresco:${frescoVersion}" + implementation "com.facebook.fresco:imagepipeline-okhttp3:${frescoVersion}" + } + + if (isGifEnabled) { + // For animated gif support + implementation "com.facebook.fresco:animated-gif:${frescoVersion}" + } + + if (isWebpEnabled) { + // For webp support + implementation "com.facebook.fresco:webpsupport:${frescoVersion}" + if (isWebpAnimatedEnabled) { + // Animated webp support + implementation "com.facebook.fresco:animated-webp:${frescoVersion}" + } + } + + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" + debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { + exclude group:'com.facebook.fbjni' + } + debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + exclude group:'com.squareup.okhttp3', module:'okhttp' + } + debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + } + + if (enableHermes) { + //noinspection GradleDynamicVersion + implementation("com.facebook.react:hermes-engine:+") { // From node_modules + exclude group:'com.facebook.fbjni' + } + } else { + implementation jscFlavor + } +} + +if (isNewArchitectureEnabled()) { + // If new architecture is enabled, we let you build RN from source + // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. + // This will be applied to all the imported transtitive dependency. + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("com.facebook.react:react-native")) + .using(project(":ReactAndroid")) + .because("On New Architecture we're building React Native from source") + substitute(module("com.facebook.react:hermes-engine")) + .using(project(":ReactAndroid:hermes-engine")) + .because("On New Architecture we're building Hermes from source") + } + } +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.implementation + into 'libs' +} + +apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); +applyNativeModulesAppBuildGradle(project) + +def isNewArchitectureEnabled() { + // To opt-in for the New Architecture, you can either: + // - Set `newArchEnabled` to true inside the `gradle.properties` file + // - Invoke gradle with `-newArchEnabled=true` + // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} diff --git a/apps/mobile/android/app/build_defs.bzl b/apps/mobile/android/app/build_defs.bzl new file mode 100644 index 000000000..fff270f8d --- /dev/null +++ b/apps/mobile/android/app/build_defs.bzl @@ -0,0 +1,19 @@ +"""Helper definitions to glob .aar and .jar targets""" + +def create_aar_targets(aarfiles): + for aarfile in aarfiles: + name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] + lib_deps.append(":" + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +def create_jar_targets(jarfiles): + for jarfile in jarfiles: + name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] + lib_deps.append(":" + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) diff --git a/apps/mobile/android/app/debug.keystore b/apps/mobile/android/app/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..364e105ed39fbfd62001429a68140672b06ec0de GIT binary patch literal 2257 zcmchYXEfYt8;7T1^dLH$VOTZ%2NOdOH5j5LYLtZ0q7x-V8_6gU5)#7dkq{HTmsfNq zB3ZqcAxeY^G10@?efK?Q&)M(qInVv!xjx+IKEL}p*K@LYvIzo#AZG>st5|P)KF1_Z;y){W{<7K{nl!CPuE z_^(!C(Ol0n8 zK13*rzAtW>(wULKPRYLd7G18F8#1P`V*9`(Poj26eOXYyBVZPno~Cvvhx7vPjAuZo zF?VD!zB~QG(!zbw#qsxT8%BSpqMZ4f70ZPn-3y$L8{EVbbN9$H`B&Z1quk9tgp5FM zuxp3pJ0b8u|3+#5bkJ4SRnCF2l7#DyLYXYY8*?OuAwK4E6J{0N=O3QNVzQ$L#FKkR zi-c@&!nDvezOV$i$Lr}iF$XEcwnybQ6WZrMKuw8gCL^U#D;q3t&HpTbqyD%vG=TeDlzCT~MXUPC|Leb-Uk+ z=vnMd(|>ld?Fh>V8poP;q;;nc@en$|rnP0ytzD&fFkCeUE^kG9Kx4wUh!!rpjwKDP zyw_e|a^x_w3E zP}}@$g>*LLJ4i0`Gx)qltL}@;mDv}D*xR^oeWcWdPkW@Uu)B^X&4W1$p6}ze!zudJ zyiLg@uggoMIArBr*27EZV7djDg@W1MaL+rcZ-lrANJQ%%>u8)ZMWU@R2qtnmG(acP z0d_^!t>}5W zpT`*2NR+0+SpTHb+6Js4b;%LJB;B_-ChhnU5py}iJtku*hm5F0!iql8Hrpcy1aYbT z1*dKC5ua6pMX@@iONI?Hpr%h;&YaXp9n!ND7-=a%BD7v&g zOO41M6EbE24mJ#S$Ui0-brR5ML%@|ndz^)YLMMV1atna{Fw<;TF@>d&F|!Z>8eg>>hkFrV)W+uv=`^F9^e zzzM2*oOjT9%gLoub%(R57p-`TXFe#oh1_{&N-YN z<}artH|m=d8TQuKSWE)Z%puU|g|^^NFwC#N=@dPhasyYjoy(fdEVfKR@cXKHZV-`06HsP`|Ftx;8(YD$fFXumLWbGnu$GMqRncXYY9mwz9$ap zQtfZB^_BeNYITh^hA7+(XNFox5WMeG_LtJ%*Q}$8VKDI_p8^pqX)}NMb`0e|wgF7D zuQACY_Ua<1ri{;Jwt@_1sW9zzdgnyh_O#8y+C;LcZq6=4e^cs6KvmK@$vVpKFGbQ= z$)Eux5C|Fx;Gtmv9^#Y-g@7Rt7*eLp5n!gJmn7&B_L$G?NCN`AP>cXQEz}%F%K;vUs{+l4Q{}eWW;ATe2 zqvXzxoIDy(u;F2q1JH7Sf;{jy_j})F+cKlIOmNfjBGHoG^CN zM|Ho&&X|L-36f}Q-obEACz`sI%2f&k>z5c$2TyTSj~vmO)BW~+N^kt`Jt@R|s!){H ze1_eCrlNaPkJQhL$WG&iRvF*YG=gXd1IyYQ9ew|iYn7r~g!wOnw;@n42>enAxBv*A zEmV*N#sxdicyNM=A4|yaOC5MByts}s_Hpfj|y<6G=o=!3S@eIFKDdpR7|FY>L&Wat&oW&cm&X~ z5Bt>Fcq(fgnvlvLSYg&o6>&fY`ODg4`V^lWWD=%oJ#Kbad2u~! zLECFS*??>|vDsNR&pH=Ze0Eo`sC_G`OjoEKVHY|wmwlX&(XBE<@sx3Hd^gtd-fNwUHsylg06p`U2y_={u}Bc + + + + + diff --git a/apps/mobile/android/app/src/debug/java/com/spacedrive/app/ReactNativeFlipper.java b/apps/mobile/android/app/src/debug/java/com/spacedrive/app/ReactNativeFlipper.java new file mode 100644 index 000000000..00c9f2231 --- /dev/null +++ b/apps/mobile/android/app/src/debug/java/com/spacedrive/app/ReactNativeFlipper.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.spacedrive.app; + +import android.content.Context; +import com.facebook.flipper.android.AndroidFlipperClient; +import com.facebook.flipper.android.utils.FlipperUtils; +import com.facebook.flipper.core.FlipperClient; +import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; +import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; +import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; +import com.facebook.flipper.plugins.inspector.DescriptorMapping; +import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; +import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; +import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; +import com.facebook.flipper.plugins.react.ReactFlipperPlugin; +import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.modules.network.NetworkingModule; +import okhttp3.OkHttpClient; + +public class ReactNativeFlipper { + public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { + if (FlipperUtils.shouldEnableFlipper(context)) { + final FlipperClient client = AndroidFlipperClient.getInstance(context); + client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); + client.addPlugin(new ReactFlipperPlugin()); + client.addPlugin(new DatabasesFlipperPlugin(context)); + client.addPlugin(new SharedPreferencesFlipperPlugin(context)); + client.addPlugin(CrashReporterPlugin.getInstance()); + NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); + NetworkingModule.setCustomClientBuilder( + new NetworkingModule.CustomClientBuilder() { + @Override + public void apply(OkHttpClient.Builder builder) { + builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); + } + }); + client.addPlugin(networkFlipperPlugin); + client.start(); + // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized + // Hence we run if after all native modules have been initialized + ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); + if (reactContext == null) { + reactInstanceManager.addReactInstanceEventListener( + new ReactInstanceManager.ReactInstanceEventListener() { + @Override + public void onReactContextInitialized(ReactContext reactContext) { + reactInstanceManager.removeReactInstanceEventListener(this); + reactContext.runOnNativeModulesQueueThread( + new Runnable() { + @Override + public void run() { + client.addPlugin(new FrescoFlipperPlugin()); + } + }); + } + }); + } else { + client.addPlugin(new FrescoFlipperPlugin()); + } + } + } +} \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/AndroidManifest.xml b/apps/mobile/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..72543be31 --- /dev/null +++ b/apps/mobile/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainActivity.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainActivity.java new file mode 100644 index 000000000..94ac57750 --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainActivity.java @@ -0,0 +1,83 @@ +package com.spacedrive.app; + +import android.os.Build; +import android.os.Bundle; + +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactActivityDelegate; +import com.facebook.react.ReactRootView; + +import expo.modules.ReactActivityDelegateWrapper; + +public class MainActivity extends ReactActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + // Set the theme to AppTheme BEFORE onCreate to support + // coloring the background, status bar, and navigation bar. + // This is required for expo-splash-screen. + setTheme(R.style.AppTheme); + super.onCreate(null); + } + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "main"; + } + + /** + * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and + * you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer + * (Paper). + */ + @Override + protected ReactActivityDelegate createReactActivityDelegate() { + return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, + new MainActivityDelegate(this, getMainComponentName()) + ); + } + + /** + * Align the back button behavior with Android S + * where moving root activities to background instead of finishing activities. + * @see onBackPressed + */ + @Override + public void invokeDefaultOnBackPressed() { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!moveTaskToBack(false)) { + // For non-root activities, use the default implementation to finish them. + super.invokeDefaultOnBackPressed(); + } + return; + } + + // Use the default back button implementation on Android S + // because it's doing more than {@link Activity#moveTaskToBack} in fact. + super.invokeDefaultOnBackPressed(); + } + + public static class MainActivityDelegate extends ReactActivityDelegate { + public MainActivityDelegate(ReactActivity activity, String mainComponentName) { + super(activity, mainComponentName); + } + + @Override + protected ReactRootView createRootView() { + ReactRootView reactRootView = new ReactRootView(getContext()); + // If you opted-in for the New Architecture, we enable the Fabric Renderer. + reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); + return reactRootView; + } + + @Override + protected boolean isConcurrentRootEnabled() { + // If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18). + // More on this on https://reactjs.org/blog/2022/03/29/react-v18.html + return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + } + } +} diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java new file mode 100644 index 000000000..8838de57a --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java @@ -0,0 +1,106 @@ +package com.spacedrive.app; + +import android.app.Application; +import android.content.Context; +import android.content.res.Configuration; +import androidx.annotation.NonNull; + +import com.facebook.react.PackageList; +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.config.ReactFeatureFlags; +import com.facebook.soloader.SoLoader; +import com.spacedrive.app.newarchitecture.MainApplicationReactNativeHost; + +import expo.modules.ApplicationLifecycleDispatcher; +import expo.modules.ReactNativeHostWrapper; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper( + this, + new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + @SuppressWarnings("UnnecessaryLocalVariable") + List packages = new PackageList(this).getPackages(); + // Packages that cannot be autolinked yet can be added manually here, for example: + // packages.add(new MyReactNativePackage()); + return packages; + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + }); + + private final ReactNativeHost mNewArchitectureNativeHost = + new ReactNativeHostWrapper(this, new MainApplicationReactNativeHost(this)); + + @Override + public ReactNativeHost getReactNativeHost() { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + return mNewArchitectureNativeHost; + } else { + return mReactNativeHost; + } + } + + @Override + public void onCreate() { + super.onCreate(); + // If you opted-in for the New Architecture, we enable the TurboModule system + ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + SoLoader.init(this, /* native exopackage */ false); + + initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); + ApplicationLifecycleDispatcher.onApplicationCreate(this); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); + } + + /** + * Loads Flipper in React Native templates. Call this in the onCreate method with something like + * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); + * + * @param context + * @param reactInstanceManager + */ + private static void initializeFlipper( + Context context, ReactInstanceManager reactInstanceManager) { + if (BuildConfig.DEBUG) { + try { + /* + We use reflection here to pick up the class that initializes Flipper, + since Flipper library is not available in release mode + */ + Class aClass = Class.forName("com.spacedrive.app.ReactNativeFlipper"); + aClass + .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) + .invoke(null, context, reactInstanceManager); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } +} diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/MainApplicationReactNativeHost.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/MainApplicationReactNativeHost.java new file mode 100644 index 000000000..1a3e3aa1d --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/MainApplicationReactNativeHost.java @@ -0,0 +1,117 @@ +package com.spacedrive.app.newarchitecture; + +import android.app.Application; +import androidx.annotation.NonNull; +import com.facebook.react.PackageList; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.bridge.JSIModulePackage; +import com.facebook.react.bridge.JSIModuleProvider; +import com.facebook.react.bridge.JSIModuleSpec; +import com.facebook.react.bridge.JSIModuleType; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.UIManager; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.react.fabric.CoreComponentsRegistry; +import com.facebook.react.fabric.EmptyReactNativeConfig; +import com.facebook.react.fabric.FabricJSIModuleProvider; +import com.facebook.react.fabric.ReactNativeConfig; +import com.facebook.react.uimanager.ViewManagerRegistry; +import com.spacedrive.app.BuildConfig; +import com.spacedrive.app.newarchitecture.components.MainComponentsRegistry; +import com.spacedrive.app.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate; +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both + * TurboModule delegates and the Fabric Renderer. + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +public class MainApplicationReactNativeHost extends ReactNativeHost { + public MainApplicationReactNativeHost(Application application) { + super(application); + } + + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + List packages = new PackageList(this).getPackages(); + // Packages that cannot be autolinked yet can be added manually here, for example: + // packages.add(new MyReactNativePackage()); + // TurboModules must also be loaded here providing a valid TurboReactPackage implementation: + // packages.add(new TurboReactPackage() { ... }); + // If you have custom Fabric Components, their ViewManagers should also be loaded here + // inside a ReactPackage. + return packages; + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + + @NonNull + @Override + protected ReactPackageTurboModuleManagerDelegate.Builder + getReactPackageTurboModuleManagerDelegateBuilder() { + // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary + // for the new architecture and to use TurboModules correctly. + return new MainApplicationTurboModuleManagerDelegate.Builder(); + } + + @Override + protected JSIModulePackage getJSIModulePackage() { + return new JSIModulePackage() { + @Override + public List getJSIModules( + final ReactApplicationContext reactApplicationContext, + final JavaScriptContextHolder jsContext) { + final List specs = new ArrayList<>(); + + // Here we provide a new JSIModuleSpec that will be responsible of providing the + // custom Fabric Components. + specs.add( + new JSIModuleSpec() { + @Override + public JSIModuleType getJSIModuleType() { + return JSIModuleType.UIManager; + } + + @Override + public JSIModuleProvider getJSIModuleProvider() { + final ComponentFactory componentFactory = new ComponentFactory(); + CoreComponentsRegistry.register(componentFactory); + + // Here we register a Components Registry. + // The one that is generated with the template contains no components + // and just provides you the one from React Native core. + MainComponentsRegistry.register(componentFactory); + + final ReactInstanceManager reactInstanceManager = getReactInstanceManager(); + + ViewManagerRegistry viewManagerRegistry = + new ViewManagerRegistry( + reactInstanceManager.getOrCreateViewManagers(reactApplicationContext)); + + return new FabricJSIModuleProvider( + reactApplicationContext, + componentFactory, + ReactNativeConfig.DEFAULT_CONFIG, + viewManagerRegistry); + } + }); + return specs; + } + }; + } +} diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/components/MainComponentsRegistry.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/components/MainComponentsRegistry.java new file mode 100644 index 000000000..0c5bce1e3 --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/components/MainComponentsRegistry.java @@ -0,0 +1,36 @@ +package com.spacedrive.app.newarchitecture.components; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.soloader.SoLoader; + +/** + * Class responsible to load the custom Fabric Components. This class has native methods and needs a + * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ + * folder for you). + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +@DoNotStrip +public class MainComponentsRegistry { + static { + SoLoader.loadLibrary("fabricjni"); + } + + @DoNotStrip private final HybridData mHybridData; + + @DoNotStrip + private native HybridData initHybrid(ComponentFactory componentFactory); + + @DoNotStrip + private MainComponentsRegistry(ComponentFactory componentFactory) { + mHybridData = initHybrid(componentFactory); + } + + @DoNotStrip + public static MainComponentsRegistry register(ComponentFactory componentFactory) { + return new MainComponentsRegistry(componentFactory); + } +} diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java new file mode 100644 index 000000000..a2cd752da --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java @@ -0,0 +1,48 @@ +package com.spacedrive.app.newarchitecture.modules; + +import com.facebook.jni.HybridData; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.soloader.SoLoader; +import java.util.List; + +/** + * Class responsible to load the TurboModules. This class has native methods and needs a + * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ + * folder for you). + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +public class MainApplicationTurboModuleManagerDelegate + extends ReactPackageTurboModuleManagerDelegate { + + private static volatile boolean sIsSoLibraryLoaded; + + protected MainApplicationTurboModuleManagerDelegate( + ReactApplicationContext reactApplicationContext, List packages) { + super(reactApplicationContext, packages); + } + + protected native HybridData initHybrid(); + + native boolean canCreateTurboModule(String moduleName); + + public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder { + protected MainApplicationTurboModuleManagerDelegate build( + ReactApplicationContext context, List packages) { + return new MainApplicationTurboModuleManagerDelegate(context, packages); + } + } + + @Override + protected synchronized void maybeLoadOtherSoLibraries() { + if (!sIsSoLibraryLoaded) { + // If you change the name of your application .so file in the Android.mk file, + // make sure you update the name here as well. + SoLoader.loadLibrary("spacedrive_appmodules"); + sIsSoLibraryLoaded = true; + } + } +} diff --git a/apps/mobile/android/app/src/main/jni/Android.mk b/apps/mobile/android/app/src/main/jni/Android.mk new file mode 100644 index 000000000..57530bb1d --- /dev/null +++ b/apps/mobile/android/app/src/main/jni/Android.mk @@ -0,0 +1,48 @@ +THIS_DIR := $(call my-dir) + +include $(REACT_ANDROID_DIR)/Android-prebuilt.mk + +# If you wish to add a custom TurboModule or Fabric component in your app you +# will have to include the following autogenerated makefile. +# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk +include $(CLEAR_VARS) + +LOCAL_PATH := $(THIS_DIR) + +# You can customize the name of your application .so file here. +LOCAL_MODULE := spacedrive_appmodules + +LOCAL_C_INCLUDES := $(LOCAL_PATH) +LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) + +# If you wish to add a custom TurboModule or Fabric component in your app you +# will have to uncomment those lines to include the generated source +# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni) +# +# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni +# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp) +# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni + +# Here you should add any native library you wish to depend on. +LOCAL_SHARED_LIBRARIES := \ + libfabricjni \ + libfbjni \ + libfolly_runtime \ + libglog \ + libjsi \ + libreact_codegen_rncore \ + libreact_debug \ + libreact_nativemodule_core \ + libreact_render_componentregistry \ + libreact_render_core \ + libreact_render_debug \ + libreact_render_graphics \ + librrc_view \ + libruntimeexecutor \ + libturbomodulejsijni \ + libyoga + +LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall + +include $(BUILD_SHARED_LIBRARY) diff --git a/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.cpp b/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.cpp new file mode 100644 index 000000000..0ac23cc62 --- /dev/null +++ b/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.cpp @@ -0,0 +1,24 @@ +#include "MainApplicationModuleProvider.h" + +#include + +namespace facebook { +namespace react { + +std::shared_ptr MainApplicationModuleProvider( + const std::string moduleName, + const JavaTurboModule::InitParams ¶ms) { + // Here you can provide your own module provider for TurboModules coming from + // either your application or from external libraries. The approach to follow + // is similar to the following (for a library called `samplelibrary`: + // + // auto module = samplelibrary_ModuleProvider(moduleName, params); + // if (module != nullptr) { + // return module; + // } + // return rncore_ModuleProvider(moduleName, params); + return rncore_ModuleProvider(moduleName, params); +} + +} // namespace react +} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.h b/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.h new file mode 100644 index 000000000..0fa43fa69 --- /dev/null +++ b/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include + +namespace facebook { +namespace react { + +std::shared_ptr MainApplicationModuleProvider( + const std::string moduleName, + const JavaTurboModule::InitParams ¶ms); + +} // namespace react +} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp b/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp new file mode 100644 index 000000000..dbbdc3d13 --- /dev/null +++ b/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp @@ -0,0 +1,45 @@ +#include "MainApplicationTurboModuleManagerDelegate.h" +#include "MainApplicationModuleProvider.h" + +namespace facebook { +namespace react { + +jni::local_ref +MainApplicationTurboModuleManagerDelegate::initHybrid( + jni::alias_ref) { + return makeCxxInstance(); +} + +void MainApplicationTurboModuleManagerDelegate::registerNatives() { + registerHybrid({ + makeNativeMethod( + "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid), + makeNativeMethod( + "canCreateTurboModule", + MainApplicationTurboModuleManagerDelegate::canCreateTurboModule), + }); +} + +std::shared_ptr +MainApplicationTurboModuleManagerDelegate::getTurboModule( + const std::string name, + const std::shared_ptr jsInvoker) { + // Not implemented yet: provide pure-C++ NativeModules here. + return nullptr; +} + +std::shared_ptr +MainApplicationTurboModuleManagerDelegate::getTurboModule( + const std::string name, + const JavaTurboModule::InitParams ¶ms) { + return MainApplicationModuleProvider(name, params); +} + +bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule( + std::string name) { + return getTurboModule(name, nullptr) != nullptr || + getTurboModule(name, {.moduleName = name}) != nullptr; +} + +} // namespace react +} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h b/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h new file mode 100644 index 000000000..f5670d9f8 --- /dev/null +++ b/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h @@ -0,0 +1,38 @@ +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +class MainApplicationTurboModuleManagerDelegate + : public jni::HybridClass< + MainApplicationTurboModuleManagerDelegate, + TurboModuleManagerDelegate> { + public: + // Adapt it to the package you used for your Java class. + static constexpr auto kJavaDescriptor = + "Lcom/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;"; + + static jni::local_ref initHybrid(jni::alias_ref); + + static void registerNatives(); + + std::shared_ptr getTurboModule( + const std::string name, + const std::shared_ptr jsInvoker) override; + std::shared_ptr getTurboModule( + const std::string name, + const JavaTurboModule::InitParams ¶ms) override; + + /** + * Test-only method. Allows user to verify whether a TurboModule can be + * created by instances of this class. + */ + bool canCreateTurboModule(std::string name); +}; + +} // namespace react +} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.cpp b/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.cpp new file mode 100644 index 000000000..8f7edffd6 --- /dev/null +++ b/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.cpp @@ -0,0 +1,61 @@ +#include "MainComponentsRegistry.h" + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {} + +std::shared_ptr +MainComponentsRegistry::sharedProviderRegistry() { + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + + // Custom Fabric Components go here. You can register custom + // components coming from your App or from 3rd party libraries here. + // + // providerRegistry->add(concreteComponentDescriptorProvider< + // AocViewerComponentDescriptor>()); + return providerRegistry; +} + +jni::local_ref +MainComponentsRegistry::initHybrid( + jni::alias_ref, + ComponentFactory *delegate) { + auto instance = makeCxxInstance(delegate); + + auto buildRegistryFunction = + [](EventDispatcher::Weak const &eventDispatcher, + ContextContainer::Shared const &contextContainer) + -> ComponentDescriptorRegistry::Shared { + auto registry = MainComponentsRegistry::sharedProviderRegistry() + ->createComponentDescriptorRegistry( + {eventDispatcher, contextContainer}); + + auto mutableRegistry = + std::const_pointer_cast(registry); + + mutableRegistry->setFallbackComponentDescriptor( + std::make_shared( + ComponentDescriptorParameters{ + eventDispatcher, contextContainer, nullptr})); + + return registry; + }; + + delegate->buildRegistryFunction = buildRegistryFunction; + return instance; +} + +void MainComponentsRegistry::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid), + }); +} + +} // namespace react +} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.h b/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.h new file mode 100644 index 000000000..a7f222542 --- /dev/null +++ b/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +class MainComponentsRegistry + : public facebook::jni::HybridClass { + public: + // Adapt it to the package you used for your Java class. + constexpr static auto kJavaDescriptor = + "Lcom/spacedrive/app/newarchitecture/components/MainComponentsRegistry;"; + + static void registerNatives(); + + MainComponentsRegistry(ComponentFactory *delegate); + + private: + static std::shared_ptr + sharedProviderRegistry(); + + static jni::local_ref initHybrid( + jni::alias_ref, + ComponentFactory *delegate); +}; + +} // namespace react +} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/OnLoad.cpp b/apps/mobile/android/app/src/main/jni/OnLoad.cpp new file mode 100644 index 000000000..c569b6e86 --- /dev/null +++ b/apps/mobile/android/app/src/main/jni/OnLoad.cpp @@ -0,0 +1,11 @@ +#include +#include "MainApplicationTurboModuleManagerDelegate.h" +#include "MainComponentsRegistry.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return facebook::jni::initialize(vm, [] { + facebook::react::MainApplicationTurboModuleManagerDelegate:: + registerNatives(); + facebook::react::MainComponentsRegistry::registerNatives(); + }); +} diff --git a/apps/mobile/android/app/src/main/res/drawable-hdpi/splashscreen_image.png b/apps/mobile/android/app/src/main/res/drawable-hdpi/splashscreen_image.png new file mode 100644 index 0000000000000000000000000000000000000000..0e07622120d1caf7c379eeb268829a8ff685af85 GIT binary patch literal 79413 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-mdpi/splashscreen_image.png b/apps/mobile/android/app/src/main/res/drawable-mdpi/splashscreen_image.png new file mode 100644 index 0000000000000000000000000000000000000000..0e07622120d1caf7c379eeb268829a8ff685af85 GIT binary patch literal 79413 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png b/apps/mobile/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png new file mode 100644 index 0000000000000000000000000000000000000000..0e07622120d1caf7c379eeb268829a8ff685af85 GIT binary patch literal 79413 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png b/apps/mobile/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png new file mode 100644 index 0000000000000000000000000000000000000000..0e07622120d1caf7c379eeb268829a8ff685af85 GIT binary patch literal 79413 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png b/apps/mobile/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png new file mode 100644 index 0000000000000000000000000000000000000000..0e07622120d1caf7c379eeb268829a8ff685af85 GIT binary patch literal 79413 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable/rn_edit_text_material.xml b/apps/mobile/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 000000000..f35d99620 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/apps/mobile/android/app/src/main/res/drawable/splashscreen.xml b/apps/mobile/android/app/src/main/res/drawable/splashscreen.xml new file mode 100644 index 000000000..c8568e162 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/drawable/splashscreen.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..3941bea9b --- /dev/null +++ b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..3941bea9b --- /dev/null +++ b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..9c9224cd65b08676a7650f4766b2b031f0e702c7 GIT binary patch literal 4303 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8e4fcba73ae5848f9847fa536fadfb49940d7807 GIT binary patch literal 2297 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 kF$K)JZJzzQgi*>c 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..ee6a34f1a141284f2231ee0feb80f333e41189e9 GIT binary patch literal 12317 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..75cc649dddc6c3944d5673e300fceb3ba1164a20 GIT binary patch literal 13996 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 icCLKhxflrga~*!sGTZiQ9Q^>ga02q;4=CE literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..92a254fc3cd6a93cdc28b0575b9749fde7771000 GIT binary patch literal 18953 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 G^Zx;MxKqdg literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..92a254fc3cd6a93cdc28b0575b9749fde7771000 GIT binary patch literal 18953 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 G^Zx;MxKqdg literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..3d004398303ea1a656be770e0e457d3431d961e4 GIT binary patch literal 21326 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 KJ16=0%l`v+3vaXl literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/values-night/colors.xml b/apps/mobile/android/app/src/main/res/values-night/colors.xml new file mode 100644 index 000000000..3c05de5be --- /dev/null +++ b/apps/mobile/android/app/src/main/res/values-night/colors.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/values/colors.xml b/apps/mobile/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..78219faf9 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + #000000 + #ffffff + #023c69 + #000000 + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/values/strings.xml b/apps/mobile/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..291d93fd7 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + Spacedrive + contain + false + automatic + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/values/styles.xml b/apps/mobile/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..f03e23f85 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/values/styles.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/apps/mobile/android/build.gradle b/apps/mobile/android/build.gradle new file mode 100644 index 000000000..a19a7b074 --- /dev/null +++ b/apps/mobile/android/build.gradle @@ -0,0 +1,59 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext { + buildToolsVersion = findProperty('android.buildToolsVersion') ?: '31.0.0' + minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21') + compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '31') + targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '31') + if (findProperty('android.kotlinVersion')) { + kotlinVersion = findProperty('android.kotlinVersion') + } + frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0' + + if (System.properties['os.arch'] == 'aarch64') { + // For M1 Users we need to use the NDK 24 which added support for aarch64 + ndkVersion = '24.0.8215888' + } else { + // Otherwise we default to the side-by-side NDK version from AGP. + ndkVersion = '21.4.7075529' + } + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath('com.android.tools.build:gradle:7.1.1') + classpath('com.facebook.react:react-native-gradle-plugin') + classpath('de.undercouch:gradle-download-task:5.0.1') + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + mavenLocal() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android')) + } + maven { + // Android JSC is installed from npm + url(new File(['node', '--print', "require.resolve('jsc-android/package.json')"].execute(null, rootDir).text.trim(), '../dist')) + } + + google() + mavenCentral { + // We don't want to fetch react-native from Maven Central as there are + // older versions over there. + content { + excludeGroup 'com.facebook.react' + } + } + maven { url 'https://www.jitpack.io' } + } +} diff --git a/apps/mobile/android/gradle.properties b/apps/mobile/android/gradle.properties new file mode 100644 index 000000000..9911ac4af --- /dev/null +++ b/apps/mobile/android/gradle.properties @@ -0,0 +1,53 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m +org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + +# Version of flipper SDK to use with React Native +FLIPPER_VERSION=0.125.0 + +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=false + +# The hosted JavaScript engine +# Supported values: expo.jsEngine = "hermes" | "jsc" +expo.jsEngine=hermes + +# Enable GIF support in React Native images (~200 B increase) +expo.gif.enabled=true +# Enable webp support in React Native images (~85 KB increase) +expo.webp.enabled=true +# Enable animated webp support (~3.4 MB increase) +# Disabled by default because iOS doesn't support animated webp +expo.webp.animated=false diff --git a/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar b/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties b/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..669386b87 --- /dev/null +++ b/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/apps/mobile/android/gradlew b/apps/mobile/android/gradlew new file mode 100755 index 000000000..1b6c78733 --- /dev/null +++ b/apps/mobile/android/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/apps/mobile/android/gradlew.bat b/apps/mobile/android/gradlew.bat new file mode 100644 index 000000000..ac1b06f93 --- /dev/null +++ b/apps/mobile/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/apps/mobile/android/settings.gradle b/apps/mobile/android/settings.gradle new file mode 100644 index 000000000..d52a28091 --- /dev/null +++ b/apps/mobile/android/settings.gradle @@ -0,0 +1,17 @@ +rootProject.name = 'Spacedrive' + +apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); +useExpoModules() + +apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); +applyNativeModulesSettingsGradle(settings) + +include ':app' +includeBuild(new File(["node", "--print", "require.resolve('react-native-gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile()) + +if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") { + include(":ReactAndroid") + project(":ReactAndroid").projectDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../ReactAndroid"); + include(":ReactAndroid:hermes-engine") + project(":ReactAndroid:hermes-engine").projectDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../ReactAndroid/hermes-engine"); +} diff --git a/apps/mobile/app.json b/apps/mobile/app.json new file mode 100644 index 000000000..629507b7c --- /dev/null +++ b/apps/mobile/app.json @@ -0,0 +1,24 @@ +{ + "expo": { + "name": "Spacedrive", + "slug": "spacedrive", + "version": "0.0.1", + "orientation": "portrait", + "jsEngine": "hermes", + "scheme": "spacedrive", + "userInterfaceStyle": "automatic", + "updates": { + "enabled": false, + "fallbackToCacheTimeout": 0 + }, + "assetBundlePatterns": ["**/*"], + "ios": { + "supportsTablet": false, + "bundleIdentifier": "com.spacedrive.app" + }, + "android": { + "package": "com.spacedrive.app" + }, + "privacy": "hidden" + } +} diff --git a/apps/mobile/babel.config.js b/apps/mobile/babel.config.js new file mode 100644 index 000000000..224bce79e --- /dev/null +++ b/apps/mobile/babel.config.js @@ -0,0 +1,7 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: ['react-native-reanimated/plugin'] + }; +}; diff --git a/apps/mobile/index.js b/apps/mobile/index.js new file mode 100644 index 000000000..018d06f91 --- /dev/null +++ b/apps/mobile/index.js @@ -0,0 +1,8 @@ +import { registerRootComponent } from 'expo'; + +import App from './src/App'; + +// registerRootComponent calls AppRegistry.registerComponent('main', () => App); +// It also ensures that whether you load the app in Expo Go or in a native build, +// the environment is set up appropriately +registerRootComponent(App); diff --git a/apps/mobile/ios/.gitignore b/apps/mobile/ios/.gitignore new file mode 100644 index 000000000..8beb34430 --- /dev/null +++ b/apps/mobile/ios/.gitignore @@ -0,0 +1,30 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace +.xcode.env.local + +# Bundle artifacts +*.jsbundle + +# CocoaPods +/Pods/ diff --git a/apps/mobile/ios/.xcode.env b/apps/mobile/ios/.xcode.env new file mode 100644 index 000000000..366234920 --- /dev/null +++ b/apps/mobile/ios/.xcode.env @@ -0,0 +1 @@ +export NODE_BINARY=$(command -v node) \ No newline at end of file diff --git a/apps/mobile/ios/Podfile b/apps/mobile/ios/Podfile new file mode 100644 index 000000000..2b5d77e16 --- /dev/null +++ b/apps/mobile/ios/Podfile @@ -0,0 +1,49 @@ +require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") +require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") +require File.join(File.dirname(`node --print "require.resolve('@react-native-community/cli-platform-ios/package.json')"`), "native_modules") + +require 'json' +podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} + +platform :ios, podfile_properties['ios.deploymentTarget'] || '13.0' +install! 'cocoapods', + :deterministic_uuids => false + +target 'Spacedrive' do + use_expo_modules! + config = use_native_modules! + + use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] + + # Flags change depending on the env values. + flags = get_default_flags() + + use_react_native!( + :path => config[:reactNativePath], + :hermes_enabled => flags[:hermes_enabled] || podfile_properties['expo.jsEngine'] == 'hermes', + :fabric_enabled => flags[:fabric_enabled], + # An absolute path to your application root. + :app_path => "#{Dir.pwd}/.." + ) + + # Uncomment to opt-in to using Flipper + # Note that if you have use_frameworks! enabled, Flipper will not work + # + # if !ENV['CI'] + # use_flipper!() + # end + + post_install do |installer| + react_native_post_install(installer) + __apply_Xcode_12_5_M1_post_install_workaround(installer) + end + + post_integrate do |installer| + begin + expo_patch_react_imports!(installer) + rescue => e + Pod::UI.warn e + end + end + +end diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock new file mode 100644 index 000000000..804fee0e9 --- /dev/null +++ b/apps/mobile/ios/Podfile.lock @@ -0,0 +1,579 @@ +PODS: + - boost (1.76.0) + - DoubleConversion (1.1.6) + - EXApplication (4.2.2): + - ExpoModulesCore + - EXConstants (13.2.3): + - ExpoModulesCore + - EXFileSystem (14.1.0): + - ExpoModulesCore + - EXFont (10.2.0): + - ExpoModulesCore + - Expo (46.0.2): + - ExpoModulesCore + - ExpoKeepAwake (10.2.0): + - ExpoModulesCore + - ExpoModulesCore (0.11.3): + - React-Core + - ReactCommon/turbomodule/core + - EXSplashScreen (0.16.1): + - ExpoModulesCore + - React-Core + - FBLazyVector (0.69.3) + - FBReactNativeSpec (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - RCTRequired (= 0.69.3) + - RCTTypeSafety (= 0.69.3) + - React-Core (= 0.69.3) + - React-jsi (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - fmt (6.2.1) + - glog (0.3.5) + - hermes-engine (0.69.3) + - libevent (2.1.12) + - RCT-Folly (2021.06.28.00-v2): + - boost + - DoubleConversion + - fmt (~> 6.2.1) + - glog + - RCT-Folly/Default (= 2021.06.28.00-v2) + - RCT-Folly/Default (2021.06.28.00-v2): + - boost + - DoubleConversion + - fmt (~> 6.2.1) + - glog + - RCT-Folly/Futures (2021.06.28.00-v2): + - boost + - DoubleConversion + - fmt (~> 6.2.1) + - glog + - libevent + - RCTRequired (0.69.3) + - RCTTypeSafety (0.69.3): + - FBLazyVector (= 0.69.3) + - RCTRequired (= 0.69.3) + - React-Core (= 0.69.3) + - React (0.69.3): + - React-Core (= 0.69.3) + - React-Core/DevSupport (= 0.69.3) + - React-Core/RCTWebSocket (= 0.69.3) + - React-RCTActionSheet (= 0.69.3) + - React-RCTAnimation (= 0.69.3) + - React-RCTBlob (= 0.69.3) + - React-RCTImage (= 0.69.3) + - React-RCTLinking (= 0.69.3) + - React-RCTNetwork (= 0.69.3) + - React-RCTSettings (= 0.69.3) + - React-RCTText (= 0.69.3) + - React-RCTVibration (= 0.69.3) + - React-bridging (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - React-jsi (= 0.69.3) + - React-callinvoker (0.69.3) + - React-Codegen (0.69.3): + - FBReactNativeSpec (= 0.69.3) + - RCT-Folly (= 2021.06.28.00-v2) + - RCTRequired (= 0.69.3) + - RCTTypeSafety (= 0.69.3) + - React-Core (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-Core (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default (= 0.69.3) + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/CoreModulesHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/Default (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/DevSupport (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default (= 0.69.3) + - React-Core/RCTWebSocket (= 0.69.3) + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-jsinspector (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTActionSheetHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTAnimationHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTBlobHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTImageHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTLinkingHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTNetworkHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTSettingsHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTTextHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTVibrationHeaders (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-Core/RCTWebSocket (0.69.3): + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-Core/Default (= 0.69.3) + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-perflogger (= 0.69.3) + - Yoga + - React-CoreModules (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - RCTTypeSafety (= 0.69.3) + - React-Codegen (= 0.69.3) + - React-Core/CoreModulesHeaders (= 0.69.3) + - React-jsi (= 0.69.3) + - React-RCTImage (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-cxxreact (0.69.3): + - boost (= 1.76.0) + - DoubleConversion + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-callinvoker (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsinspector (= 0.69.3) + - React-logger (= 0.69.3) + - React-perflogger (= 0.69.3) + - React-runtimeexecutor (= 0.69.3) + - React-hermes (0.69.3): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2021.06.28.00-v2) + - RCT-Folly/Futures (= 2021.06.28.00-v2) + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-jsiexecutor (= 0.69.3) + - React-jsinspector (= 0.69.3) + - React-perflogger (= 0.69.3) + - React-jsi (0.69.3): + - boost (= 1.76.0) + - DoubleConversion + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-jsi/Default (= 0.69.3) + - React-jsi/Default (0.69.3): + - boost (= 1.76.0) + - DoubleConversion + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-jsiexecutor (0.69.3): + - DoubleConversion + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-perflogger (= 0.69.3) + - React-jsinspector (0.69.3) + - React-logger (0.69.3): + - glog + - react-native-safe-area-context (4.3.1): + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React + - ReactCommon/turbomodule/core + - React-perflogger (0.69.3) + - React-RCTActionSheet (0.69.3): + - React-Core/RCTActionSheetHeaders (= 0.69.3) + - React-RCTAnimation (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - RCTTypeSafety (= 0.69.3) + - React-Codegen (= 0.69.3) + - React-Core/RCTAnimationHeaders (= 0.69.3) + - React-jsi (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-RCTBlob (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - React-Codegen (= 0.69.3) + - React-Core/RCTBlobHeaders (= 0.69.3) + - React-Core/RCTWebSocket (= 0.69.3) + - React-jsi (= 0.69.3) + - React-RCTNetwork (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-RCTImage (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - RCTTypeSafety (= 0.69.3) + - React-Codegen (= 0.69.3) + - React-Core/RCTImageHeaders (= 0.69.3) + - React-jsi (= 0.69.3) + - React-RCTNetwork (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-RCTLinking (0.69.3): + - React-Codegen (= 0.69.3) + - React-Core/RCTLinkingHeaders (= 0.69.3) + - React-jsi (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-RCTNetwork (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - RCTTypeSafety (= 0.69.3) + - React-Codegen (= 0.69.3) + - React-Core/RCTNetworkHeaders (= 0.69.3) + - React-jsi (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-RCTSettings (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - RCTTypeSafety (= 0.69.3) + - React-Codegen (= 0.69.3) + - React-Core/RCTSettingsHeaders (= 0.69.3) + - React-jsi (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-RCTText (0.69.3): + - React-Core/RCTTextHeaders (= 0.69.3) + - React-RCTVibration (0.69.3): + - RCT-Folly (= 2021.06.28.00-v2) + - React-Codegen (= 0.69.3) + - React-Core/RCTVibrationHeaders (= 0.69.3) + - React-jsi (= 0.69.3) + - ReactCommon/turbomodule/core (= 0.69.3) + - React-runtimeexecutor (0.69.3): + - React-jsi (= 0.69.3) + - ReactCommon/turbomodule/core (0.69.3): + - DoubleConversion + - glog + - RCT-Folly (= 2021.06.28.00-v2) + - React-bridging (= 0.69.3) + - React-callinvoker (= 0.69.3) + - React-Core (= 0.69.3) + - React-cxxreact (= 0.69.3) + - React-jsi (= 0.69.3) + - React-logger (= 0.69.3) + - React-perflogger (= 0.69.3) + - RNCAsyncStorage (1.17.7): + - React-Core + - RNGestureHandler (2.5.0): + - React-Core + - RNReanimated (2.9.1): + - DoubleConversion + - FBLazyVector + - FBReactNativeSpec + - glog + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-Core/DevSupport + - React-Core/RCTWebSocket + - React-CoreModules + - React-cxxreact + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-RCTActionSheet + - React-RCTAnimation + - React-RCTBlob + - React-RCTImage + - React-RCTLinking + - React-RCTNetwork + - React-RCTSettings + - React-RCTText + - ReactCommon/turbomodule/core + - Yoga + - RNScreens (3.15.0): + - React-Core + - React-RCTImage + - RNSVG (12.4.3): + - React-Core + - Yoga (1.14.0) + +DEPENDENCIES: + - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) + - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - "EXApplication (from `../node_modules/.pnpm/expo-application@4.2.2_expo@46.0.2/node_modules/expo-application/ios`)" + - "EXConstants (from `../node_modules/.pnpm/expo-constants@13.2.3_expo@46.0.2/node_modules/expo-constants/ios`)" + - "EXFileSystem (from `../node_modules/.pnpm/expo-file-system@14.1.0_expo@46.0.2/node_modules/expo-file-system/ios`)" + - "EXFont (from `../node_modules/.pnpm/expo-font@10.2.0_expo@46.0.2/node_modules/expo-font/ios`)" + - "Expo (from `../node_modules/.pnpm/expo@46.0.2_@babel+core@7.18.10/node_modules/expo`)" + - "ExpoKeepAwake (from `../node_modules/.pnpm/expo-keep-awake@10.2.0_expo@46.0.2/node_modules/expo-keep-awake/ios`)" + - "ExpoModulesCore (from `../node_modules/.pnpm/expo-modules-core@0.11.3/node_modules/expo-modules-core/ios`)" + - "EXSplashScreen (from `../node_modules/.pnpm/expo-splash-screen@0.16.1_expo@46.0.2/node_modules/expo-splash-screen/ios`)" + - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) + - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - hermes-engine (from `../node_modules/react-native/sdks/hermes/hermes-engine.podspec`) + - libevent (~> 2.1.12) + - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) + - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../node_modules/react-native/`) + - React-bridging (from `../node_modules/react-native/ReactCommon`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - React-Codegen (from `build/generated/ios`) + - React-Core (from `../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../node_modules/react-native/`) + - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) + - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) + - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) + - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" + - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) + - RNReanimated (from `../node_modules/react-native-reanimated`) + - RNScreens (from `../node_modules/react-native-screens`) + - RNSVG (from `../node_modules/react-native-svg`) + - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + +SPEC REPOS: + trunk: + - fmt + - libevent + +EXTERNAL SOURCES: + boost: + :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" + DoubleConversion: + :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + EXApplication: + :path: "../node_modules/.pnpm/expo-application@4.2.2_expo@46.0.2/node_modules/expo-application/ios" + EXConstants: + :path: "../node_modules/.pnpm/expo-constants@13.2.3_expo@46.0.2/node_modules/expo-constants/ios" + EXFileSystem: + :path: "../node_modules/.pnpm/expo-file-system@14.1.0_expo@46.0.2/node_modules/expo-file-system/ios" + EXFont: + :path: "../node_modules/.pnpm/expo-font@10.2.0_expo@46.0.2/node_modules/expo-font/ios" + Expo: + :path: "../node_modules/.pnpm/expo@46.0.2_@babel+core@7.18.10/node_modules/expo" + ExpoKeepAwake: + :path: "../node_modules/.pnpm/expo-keep-awake@10.2.0_expo@46.0.2/node_modules/expo-keep-awake/ios" + ExpoModulesCore: + :path: "../node_modules/.pnpm/expo-modules-core@0.11.3/node_modules/expo-modules-core/ios" + EXSplashScreen: + :path: "../node_modules/.pnpm/expo-splash-screen@0.16.1_expo@46.0.2/node_modules/expo-splash-screen/ios" + FBLazyVector: + :path: "../node_modules/react-native/Libraries/FBLazyVector" + FBReactNativeSpec: + :path: "../node_modules/react-native/React/FBReactNativeSpec" + glog: + :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + hermes-engine: + :podspec: "../node_modules/react-native/sdks/hermes/hermes-engine.podspec" + RCT-Folly: + :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + RCTRequired: + :path: "../node_modules/react-native/Libraries/RCTRequired" + RCTTypeSafety: + :path: "../node_modules/react-native/Libraries/TypeSafety" + React: + :path: "../node_modules/react-native/" + React-bridging: + :path: "../node_modules/react-native/ReactCommon" + React-callinvoker: + :path: "../node_modules/react-native/ReactCommon/callinvoker" + React-Codegen: + :path: build/generated/ios + React-Core: + :path: "../node_modules/react-native/" + React-CoreModules: + :path: "../node_modules/react-native/React/CoreModules" + React-cxxreact: + :path: "../node_modules/react-native/ReactCommon/cxxreact" + React-hermes: + :path: "../node_modules/react-native/ReactCommon/hermes" + React-jsi: + :path: "../node_modules/react-native/ReactCommon/jsi" + React-jsiexecutor: + :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + React-jsinspector: + :path: "../node_modules/react-native/ReactCommon/jsinspector" + React-logger: + :path: "../node_modules/react-native/ReactCommon/logger" + react-native-safe-area-context: + :path: "../node_modules/react-native-safe-area-context" + React-perflogger: + :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-RCTActionSheet: + :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + React-RCTAnimation: + :path: "../node_modules/react-native/Libraries/NativeAnimation" + React-RCTBlob: + :path: "../node_modules/react-native/Libraries/Blob" + React-RCTImage: + :path: "../node_modules/react-native/Libraries/Image" + React-RCTLinking: + :path: "../node_modules/react-native/Libraries/LinkingIOS" + React-RCTNetwork: + :path: "../node_modules/react-native/Libraries/Network" + React-RCTSettings: + :path: "../node_modules/react-native/Libraries/Settings" + React-RCTText: + :path: "../node_modules/react-native/Libraries/Text" + React-RCTVibration: + :path: "../node_modules/react-native/Libraries/Vibration" + React-runtimeexecutor: + :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + ReactCommon: + :path: "../node_modules/react-native/ReactCommon" + RNCAsyncStorage: + :path: "../node_modules/@react-native-async-storage/async-storage" + RNGestureHandler: + :path: "../node_modules/react-native-gesture-handler" + RNReanimated: + :path: "../node_modules/react-native-reanimated" + RNScreens: + :path: "../node_modules/react-native-screens" + RNSVG: + :path: "../node_modules/react-native-svg" + Yoga: + :path: "../node_modules/react-native/ReactCommon/yoga" + +SPEC CHECKSUMS: + boost: a7c83b31436843459a1961bfd74b96033dc77234 + DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 + EXApplication: e418d737a036e788510f2c4ad6c10a7d54d18586 + EXConstants: 75c40827af38bd6bfcf69f880a5b45037eeff9c9 + EXFileSystem: 927e0a8885aa9c49e50fc38eaba2c2389f2f1019 + EXFont: a5d80bd9b3452b2d5abbce2487da89b0150e6487 + Expo: a2d9d4d17b9c97beab797c54220b305708f60e87 + ExpoKeepAwake: 0e8f18142e71bbf2c7f6aa66ebed249ba1420320 + ExpoModulesCore: 8303cc952788be09fc6eab62815d257016ae6dec + EXSplashScreen: 31ab6df6d23e97e074d1330224741979943f1d82 + FBLazyVector: 1d83d91816fa605d16227a83f1b2e71c8df09d22 + FBReactNativeSpec: 06454fe46192886c1bc472593057015292ba37ee + fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 + glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a + hermes-engine: ff1ba576165861a94a0d101b0a351a8ca2149f36 + libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 + RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a + RCTRequired: 66822c147facf02f7774af99825e0a31e39df42e + RCTTypeSafety: 309306c4e711b14a83c55c2816a6cc490ec19827 + React: a779632422a918b26db4f1b57225a41c14d20525 + React-bridging: 96055aa45f0417898d7833e251f4ae79d28acef7 + React-callinvoker: 02df4d620df286381ff3f99180fb24feceaf01cc + React-Codegen: 06613a5e753c3af2dca0d6e7dd02944a3d77c3f6 + React-Core: 638d54d64048aa635e7c583fb0d8425206f446b4 + React-CoreModules: f706ec2a1939387517cadc6ce0d2ef0f20fccb53 + React-cxxreact: ec183b7f6fec01e7167f38c1c64a03f68dca7fb2 + React-hermes: a97962948f74aaefffd4fe00bdafafbc245b08af + React-jsi: ed7dc77f5193dca9c73cec90bfec409e7ddfe401 + React-jsiexecutor: 1842ca163b160aeb224d2c65b2a60c393b273c67 + React-jsinspector: bb2605f98aada5d81f3494690da3ef3b4ff3b716 + React-logger: 23a50ef4c18bf9adbb51e2c979318e6b3a2e44a1 + react-native-safe-area-context: 6c12e3859b6f27b25de4fee8201cfb858432d8de + React-perflogger: 39d2ba8cbcac54d1bb1d9a980dab348e96aef467 + React-RCTActionSheet: b1ad907a2c8f8e4d037148ca507b7f2d6ab1c66d + React-RCTAnimation: 914a9ba46fb6e7376f7709c7ce825d53b47ca2ee + React-RCTBlob: de62fd5edc5c36951f0b113bf252eb43b7131f79 + React-RCTImage: aa0749a8d748b34942c7e71ac5d9f42be8b70cf3 + React-RCTLinking: 595a9f8fbf4d6634bff28d1175b3523b61466612 + React-RCTNetwork: 0559fd0fccb01f89c638baa43c8d185dc8008626 + React-RCTSettings: 8e492a25a62f1ef6323f82ce652ae87fa59c82ca + React-RCTText: 17457cde6ef8832ba43c886baebb6627c5d7ed18 + React-RCTVibration: dd8099eb46e9cee4692934bc8cbe5e9a4f5e8d31 + React-runtimeexecutor: 607eb048e22a16388c908ee1f6644200e8d1e19b + ReactCommon: af7636436b382db7cde4583bbd642f0978e6e3ed + RNCAsyncStorage: d81ee5c3db1060afd49ea7045ad460eff82d2b7d + RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50 + RNReanimated: 2cf7451318bb9cc430abeec8d67693f9cf4e039c + RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7 + RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9 + Yoga: 44c64131616253fa83366295acdbce3d14926041 + +PODFILE CHECKSUM: b77befb1871220c1a94408eeae0857d78b685698 + +COCOAPODS: 1.11.3 diff --git a/apps/mobile/ios/Podfile.properties.json b/apps/mobile/ios/Podfile.properties.json new file mode 100644 index 000000000..35120e838 --- /dev/null +++ b/apps/mobile/ios/Podfile.properties.json @@ -0,0 +1,3 @@ +{ + "expo.jsEngine": "hermes" +} diff --git a/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj b/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj new file mode 100644 index 000000000..3a8ac6a59 --- /dev/null +++ b/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj @@ -0,0 +1,512 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; + 519D1250147D911454D7DB76 /* libPods-Spacedrive.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F276D840CDBF9D0D9D548D /* libPods-Spacedrive.a */; }; + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; + C95AE27BB525EFF3F02CEC11 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E71A6EFA9EA8F4C11F42FA /* ExpoModulesProvider.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* Spacedrive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Spacedrive.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 56E71A6EFA9EA8F4C11F42FA /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Spacedrive/ExpoModulesProvider.swift"; sourceTree = ""; }; + 6C2E3173556A471DD304B334 /* Pods-mobilenew.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-mobilenew.debug.xcconfig"; path = "Target Support Files/Pods-mobilenew/Pods-mobilenew.debug.xcconfig"; sourceTree = ""; }; + 78E58D73F5113B1BD543EE4D /* Pods-Spacedrive.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Spacedrive.debug.xcconfig"; path = "Target Support Files/Pods-Spacedrive/Pods-Spacedrive.debug.xcconfig"; sourceTree = ""; }; + 7A4D352CD337FB3A3BF06240 /* Pods-mobilenew.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-mobilenew.release.xcconfig"; path = "Target Support Files/Pods-mobilenew/Pods-mobilenew.release.xcconfig"; sourceTree = ""; }; + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SplashScreen.storyboard; sourceTree = ""; }; + BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Expo.plist; path = Supporting/Expo.plist; sourceTree = ""; }; + D8F0094C07EEE7528760BD08 /* Pods-Spacedrive.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Spacedrive.release.xcconfig"; path = "Target Support Files/Pods-Spacedrive/Pods-Spacedrive.release.xcconfig"; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + F3F276D840CDBF9D0D9D548D /* libPods-Spacedrive.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Spacedrive.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 519D1250147D911454D7DB76 /* libPods-Spacedrive.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* Spacedrive */ = { + isa = PBXGroup; + children = ( + BB2F792B24A3F905000567C9 /* Supporting */, + 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.mm */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB71A68108700A75B9A /* main.m */, + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, + ); + path = Spacedrive; + sourceTree = ""; + }; + 2541029223E7A78AF70DBF1F /* Spacedrive */ = { + isa = PBXGroup; + children = ( + 56E71A6EFA9EA8F4C11F42FA /* ExpoModulesProvider.swift */, + ); + name = Spacedrive; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + F3F276D840CDBF9D0D9D548D /* libPods-Spacedrive.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* Spacedrive */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + D65327D7A22EEC0BE12398D9 /* Pods */, + D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* Spacedrive.app */, + ); + name = Products; + sourceTree = ""; + }; + BB2F792B24A3F905000567C9 /* Supporting */ = { + isa = PBXGroup; + children = ( + BB2F792C24A3F905000567C9 /* Expo.plist */, + ); + name = Supporting; + sourceTree = ""; + }; + D65327D7A22EEC0BE12398D9 /* Pods */ = { + isa = PBXGroup; + children = ( + 6C2E3173556A471DD304B334 /* Pods-mobilenew.debug.xcconfig */, + 7A4D352CD337FB3A3BF06240 /* Pods-mobilenew.release.xcconfig */, + 78E58D73F5113B1BD543EE4D /* Pods-Spacedrive.debug.xcconfig */, + D8F0094C07EEE7528760BD08 /* Pods-Spacedrive.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = { + isa = PBXGroup; + children = ( + 2541029223E7A78AF70DBF1F /* Spacedrive */, + ); + name = ExpoModulesProviders; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* Spacedrive */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Spacedrive" */; + buildPhases = ( + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, + FD10A7F022414F080027D42C /* Start Packager */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, + 0D580678BEFB3E7EF09A2CFE /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Spacedrive; + productName = mobilenew; + productReference = 13B07F961A680F5B00A75B9A /* Spacedrive.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1130; + TargetAttributes = { + 13B07F861A680F5B00A75B9A = { + DevelopmentTeam = 72SE38W7T9; + LastSwiftMigration = 1250; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Spacedrive" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* Spacedrive */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\n`node --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n"; + }; + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Spacedrive-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 0D580678BEFB3E7EF09A2CFE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + FD10A7F022414F080027D42C /* Start Packager */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Start Packager"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > `node --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/.packager.env'\"`\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open `node --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/launchPackager.command'\"` || echo \"Can't start packager automatically\"\n fi\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + C95AE27BB525EFF3F02CEC11 /* ExpoModulesProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 78E58D73F5113B1BD543EE4D /* Pods-Spacedrive.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Spacedrive/Spacedrive.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 72SE38W7T9; + ENABLE_BITCODE = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "FB_SONARKIT_ENABLED=1", + ); + INFOPLIST_FILE = Spacedrive/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.spacedrive.app; + PRODUCT_NAME = Spacedrive; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D8F0094C07EEE7528760BD08 /* Pods-Spacedrive.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Spacedrive/Spacedrive.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 72SE38W7T9; + INFOPLIST_FILE = Spacedrive/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = com.spacedrive.app; + PRODUCT_NAME = Spacedrive; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = NO; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Spacedrive" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Spacedrive" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/apps/mobile/ios/Spacedrive.xcodeproj/xcshareddata/xcschemes/Spacedrive.xcscheme b/apps/mobile/ios/Spacedrive.xcodeproj/xcshareddata/xcschemes/Spacedrive.xcscheme new file mode 100644 index 000000000..05655c640 --- /dev/null +++ b/apps/mobile/ios/Spacedrive.xcodeproj/xcshareddata/xcschemes/Spacedrive.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/mobile/ios/Spacedrive/AppDelegate.h b/apps/mobile/ios/Spacedrive/AppDelegate.h new file mode 100644 index 000000000..f7d297204 --- /dev/null +++ b/apps/mobile/ios/Spacedrive/AppDelegate.h @@ -0,0 +1,9 @@ +#import +#import +#import + +#import + +@interface AppDelegate : EXAppDelegateWrapper + +@end diff --git a/apps/mobile/ios/Spacedrive/AppDelegate.mm b/apps/mobile/ios/Spacedrive/AppDelegate.mm new file mode 100644 index 000000000..a6e13e11a --- /dev/null +++ b/apps/mobile/ios/Spacedrive/AppDelegate.mm @@ -0,0 +1,166 @@ +#import "AppDelegate.h" + +#import +#import +#import +#import +#import + +#import + +#if RCT_NEW_ARCH_ENABLED +#import +#import +#import +#import +#import +#import + +#import + +static NSString *const kRNConcurrentRoot = @"concurrentRoot"; + +@interface AppDelegate () { + RCTTurboModuleManager *_turboModuleManager; + RCTSurfacePresenterBridgeAdapter *_bridgeAdapter; + std::shared_ptr _reactNativeConfig; + facebook::react::ContextContainer::Shared _contextContainer; +} +@end +#endif + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + RCTAppSetupPrepareApp(application); + + RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions]; + +#if RCT_NEW_ARCH_ENABLED + _contextContainer = std::make_shared(); + _reactNativeConfig = std::make_shared(); + _contextContainer->insert("ReactNativeConfig", _reactNativeConfig); + _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer]; + bridge.surfacePresenter = _bridgeAdapter.surfacePresenter; +#endif + + NSDictionary *initProps = [self prepareInitialProps]; + UIView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:initProps]; + + rootView.backgroundColor = [UIColor whiteColor]; + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [self.reactDelegate createRootViewController]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + + [super application:application didFinishLaunchingWithOptions:launchOptions]; + + return YES; +} + +- (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge +{ + // If you'd like to export some custom RCTBridgeModules, add them here! + return @[]; +} + +/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off. +/// +/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html +/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture). +/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`. +- (BOOL)concurrentRootEnabled +{ + // Switch this bool to turn on and off the concurrent root + return true; +} + +- (NSDictionary *)prepareInitialProps +{ + NSMutableDictionary *initProps = [NSMutableDictionary new]; +#if RCT_NEW_ARCH_ENABLED + initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]); +#endif + return initProps; +} + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge +{ +#if DEBUG + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; +#else + return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif +} + +// Linking API +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { + return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; +} + +// Universal Links +- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { + BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; + return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error +{ + return [super application:application didFailToRegisterForRemoteNotificationsWithError:error]; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler +{ + return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} + +#if RCT_NEW_ARCH_ENABLED + +#pragma mark - RCTCxxBridgeDelegate + +- (std::unique_ptr)jsExecutorFactoryForBridge:(RCTBridge *)bridge +{ + _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge + delegate:self + jsInvoker:bridge.jsCallInvoker]; + return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager); +} + +#pragma mark RCTTurboModuleManagerDelegate + +- (Class)getModuleClassFromName:(const char *)name +{ + return RCTCoreModulesClassProvider(name); +} + +- (std::shared_ptr)getTurboModule:(const std::string &)name + jsInvoker:(std::shared_ptr)jsInvoker +{ + return nullptr; +} + +- (std::shared_ptr)getTurboModule:(const std::string &)name + initParams: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return nullptr; +} + +- (id)getModuleInstanceFromClass:(Class)moduleClass +{ + return RCTAppSetupDefaultModuleFromClass(moduleClass); +} + +#endif + +@end diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..31eba8cc9b31037ed616506757c7d5c041493ae6 GIT binary patch literal 733 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e0312ff81754ae67d46b18a18ba30037310d11c6 GIT binary patch literal 3645 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..30cf544d11e470aab2b7383bf0065b2dd4758e48 GIT binary patch literal 3471 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 SQ}B1jAw3-f?GjBp;{O4av50^G literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c74ab91fe68a7a9bf2180b5dbc9a93b5eed64dd8 GIT binary patch literal 1949 zcmV;O2V(e%P)|zr#AzL}Fs0HKwVeEqEpyL^j1E(kghV7kZiE!)cx&!!GlM7cxdoXVY)5i?%B%1OyTWI~b<&NT7DucpnUIi#)3!YKyXmPf9qc_}r*{|2-C;h|v05Fa z1)7T)Ic7dhJeBQCw)bW|gTyPV+#tz>fWRB;6~F!Z3s=8-aM!qhdVjeb#(61Yu{xkp z%wPd?G$=_6+4Je&%;{3v{b|N4sGO7#^oi9q9{%D}hhM&SVdu>9)XrezpfX6sqyhts zFv180nE;#6Bz}_Hqu^4SaVy64l1vD^vA)L7zV_swzH)hQvsc$Uijgcz2}w|(Kmp^J zpuh;DvIwdC+}f{|$*mYS2(q(X@z}4f{qhTs?reAQxvz)bO7G^O3|=M>1axq42oe$! z5y~H^{e@dG{w2wTpf`@L^8L^M-_B|@U-T&3tu`BNC#Ft!vV%iFhY2Rw!j@c-Tf&JT zf}FJZuH3mP z1vdgB@Qyd4kxlD|SzN@78&qDBK#)P-a(E^F{OiZ>ir0GA6My(ytT)NfQ-YSc!40>An)_mg z84@W<#|OL+6)%Jc5s3%h@lI<@Yu1+;?%@U%NFc}{I(^GCm!BuYQyq+KqzK{KI)^pq zAX0cTbF$(UFL)u000$2|@J{b)!RqPUyC8|{RT7d6(uq#ra(HzWM1w*YDMEyBrRPEM z9eE*=9xRwKHqfN))=$4Hpg$0iM}2ySbLe36&s&bh48j1*czodZTHh^=Qr1rBFr^i3jfM z(pF^}*oqB|L21Zv(Txr+Iz-~&@)&)2u5d!lAf4<)CmkiPKX*1~w)TU$t<9E-8LF9) zL=Z3XBWrU_g{@JOA`~R_q(O%cPKF|@5Bq-V4JL7enn60ziB5J3EN0~m7w>)Yktder zu#_E(nX-~4$smI8b!sE4*oX;B1PK8>>Cj0hU7j)B`!+`b&&r#z%D|n;ZFT2;@y53g5+_-7 zq7$9yM2BFk>@M@62S2_Uj;_|roBDjKhpnudZMwCt-ih9_H+rW9Eon*1Y?+5vkPBYV5~!2e!4T3!3`4}TEyef5QT z^e7${p$svA5+#|+P&mEYcB{jUu6*zlXW#eH*9-#^B#0Z-Xc=A6aECxa3KR1=vi#VC zAK2U9`|h{DJ+a!e!=;@a{8Wuy#ZnrQEB@4s}eKId(_44uyJ-~+#4&M$>W%1xlL5(#LI@JfSS%QZyzecqJAeMOzj^fWhadjM zAAb8--&{5=RpwEKFbf!m;r{#Ye&B)o-u})rJNt}_w1G4bw^F0yop3TmVj>|SKwx4V z8AcY1{GUtb-|@~juD5>q$!DK=?uDbxRz-XJ%RBEpb^gNX-F?lM%oda}DWriwAa1I9 zC!7c}5CljBC{d!sgcatM*@DRQw6o{Vrp{tm#Rlp~ol!?@KqWCSFc64i;-ng#2qFX| z7zB_=2uO}4W>}#VW(5i=P_T+Bs;EFo3=9ke0&z^-RCOnuOcDeM2?>!XL5Pn<7ph8Iu3J44Y;+S{^HG)VbFG~@;L`X;k$Vej#S;>k9s8FFoNeUPU48+UCE327G zNM0fW3{s>>5kiG5P@qCZ0~(-0O$rz)AP|US;?<}L2?+=UB8?OnX;4u`0~*kP1~foL jHC4cnfq{6Lc$NMRRkD{63;7o*00000NkvXXu0mjf^Ua+3 literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b507515fc6d81ab1891b715849023b231e7dce4a GIT binary patch literal 5587 zcmZ8ldpy(s_b)chY_*t?QKQ8O2@N3|EhG1oOSxaGnd=9YTxPoPA(u}|?ziaTGD76H=2ksxM?Jb=Tuo+yKnAu+63BEa($_>G13hT>A;&3sOxaPa|YYZUn;>8H!{z$9r~*Z=?3br@@0kt5nHH*CfGgnwt( zGVuE7I&Y+7_8S7+=OPuBL#5`&lBjD9R)MCY>(Y^(hAO2~uA7XBW9f^Bx0M!^Z^?|1 z;wlp>)t9Hg-TkJLw<9xR^pa{yVXLs&g3T7D^y6~G(xe16waulVX_fA}d5;lqwFykO;AxPdoJGf0z2|CsqWSZ-1LkL&c&hKxvNQBlOJ*og;y_;=KcD!z&;WsjwH zR_g_70uvv#s|U2$Y!3?@%<|lKtZ=vt zf3+>f76Da@ckA&}lIm@!{8MqrR%650ST23KYPCgdv%lGL#cB3J^vgKwc+(g9a?W}f zOZ%|>+y@Xy?x4Q)mLA(LE1KVDgu|K4qJB1C{6+nJYW$F?=Rnu(p(Lpdr`>7a`@6S- z+d2Jj2knab_2&GYWG`R5@j~RW=?ikS^T*JL*`KjcjADiaSNx4ep-H=@jSKGr)Cw6~XIYUGg1;KW*KRB?4P)SlnmxqteDZDUHCnr$q;|&K>`G_t zZu#79R8@1RX{Wz9eM9!ei6ai?GUg|s+&r?yA48!S%M2WI-;J}^KgFh4%@@_Jetu^a zE@N8~A(VxKF*r*NWj^IYTRg|sdUSV}CeGa%9R87-mzP)7Ws@!|8=h-+{7k&QjJcUO zlpD^2Mk9oaNn|qa8a&fmgmERJ`Im*2=THEz`9i0x#-WPnzv@&cws>mROVfkb1~qQa z5C2$R_Or_y@w1a3)pj z7hvLH5+fll!K%JlK#|>rwBuA2UzZOu%-TlXEX@{jT49R^8O6=r-O;bM1u9V&n)CL2lE4^6dWRnlDBE+#a?C#LQquq`4l(rwMddo&n z%AWVM`p0>ENoj2^JDp6gG(|(mD2M}_$txg9E3W0i78d{H5x^#6DA}yqrFoZaeE)kw zeY#Zp;hen#(6){Y9`#QN7+S_`zN3z#BxmC~jHk)~GPk6Bg zPm61rNTQ`fy&$pA-m&hyDQ%Y1ac!%+RZqpjVv7PyS0-V5p+Z9I+k-))i-$s1{Js|& zcdCneNG7?m;WI~OK9{lRv;>cmGgrF|jh`wwXxH&z4J;iZkXJ0d>jf>YdFQI!u6r0Q zfK?|F4K^-a3qp)dW2z&Yo;>JGs3@L)H}bW!bG;4BP`l&(_Dw z5|1>tMzcfK@`|QirZnZvo)HBkU#m_|zf|&xra5P08ZHRFHKiaG?IRfX7$mYov4W*A zf`@Ec$zn}Cj8-yA#q}QZUK1TT$6x3;eDYz?Ljuvaq~Nts&|3Fy;8NwHb8%hf^-cws z=}&@Dg{7axXB~1eFY~h^(b9=oa4v4Hh=^J?n{BTk%;c$Wc#f4K_AljNvCm2m`TWtr z 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 CV8^Eb literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4ce4dc33ebb2a4c5743f625211639c83b0d161f3 GIT binary patch literal 10559 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 CV8^Eb literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..da848157a94dade409a9fefc493c34c2c693fc9e GIT binary patch literal 19556 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d37a38fe56e17d65435c3ff466b8aa90f6f6b54f GIT binary patch literal 14875 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 aN$+Vv=Z|l`f&cXR*Jb@{dNiFIQU3>@ROg!j literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1f8761d3a99d1b6c1d738bd8216c0e7ef0f45050 GIT binary patch literal 17403 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/Contents.json b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..f920cb0ec --- /dev/null +++ b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images": [ + { + "idiom": "iphone", + "size": "20x20", + "scale": "2x", + "filename": "App-Icon-20x20@2x.png" + }, + { + "idiom": "iphone", + "size": "20x20", + "scale": "3x", + "filename": "App-Icon-20x20@3x.png" + }, + { + "idiom": "iphone", + "size": "29x29", + "scale": "1x", + "filename": "App-Icon-29x29@1x.png" + }, + { + "idiom": "iphone", + "size": "29x29", + "scale": "2x", + "filename": "App-Icon-29x29@2x.png" + }, + { + "idiom": "iphone", + "size": "29x29", + "scale": "3x", + "filename": "App-Icon-29x29@3x.png" + }, + { + "idiom": "iphone", + "size": "40x40", + "scale": "2x", + "filename": "App-Icon-40x40@2x.png" + }, + { + "idiom": "iphone", + "size": "40x40", + "scale": "3x", + "filename": "App-Icon-40x40@3x.png" + }, + { + "idiom": "iphone", + "size": "60x60", + "scale": "2x", + "filename": "App-Icon-60x60@2x.png" + }, + { + "idiom": "iphone", + "size": "60x60", + "scale": "3x", + "filename": "App-Icon-60x60@3x.png" + }, + { + "idiom": "ipad", + "size": "20x20", + "scale": "1x", + "filename": "App-Icon-20x20@1x.png" + }, + { + "idiom": "ipad", + "size": "20x20", + "scale": "2x", + "filename": "App-Icon-20x20@2x.png" + }, + { + "idiom": "ipad", + "size": "29x29", + "scale": "1x", + "filename": "App-Icon-29x29@1x.png" + }, + { + "idiom": "ipad", + "size": "29x29", + "scale": "2x", + "filename": "App-Icon-29x29@2x.png" + }, + { + "idiom": "ipad", + "size": "40x40", + "scale": "1x", + "filename": "App-Icon-40x40@1x.png" + }, + { + "idiom": "ipad", + "size": "40x40", + "scale": "2x", + "filename": "App-Icon-40x40@2x.png" + }, + { + "idiom": "ipad", + "size": "76x76", + "scale": "1x", + "filename": "App-Icon-76x76@1x.png" + }, + { + "idiom": "ipad", + "size": "76x76", + "scale": "2x", + "filename": "App-Icon-76x76@2x.png" + }, + { + "idiom": "ipad", + "size": "83.5x83.5", + "scale": "2x", + "filename": "App-Icon-83.5x83.5@2x.png" + }, + { + "idiom": "ios-marketing", + "size": "1024x1024", + "scale": "1x", + "filename": "ItunesArtwork@2x.png" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/apps/mobile/ios/Spacedrive/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..962808d1410f1254ebe9bea969c2f2b5db85aba9 GIT binary patch literal 379434 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%0DC|AMVI;EeRA|i&}#%yc!2;}D^K_FwSdW&lP( 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 z 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

fW7w^=Mf=>Zy%&s@sH&<90F(-Z4FqF!mCR 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 z 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

T9!%pUv7H^CBs<WI9G?w0L2+0bx> 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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%0DgTM9I%FpLA!IjAR35LA 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 GZ~s40t|!|7 literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/Contents.json b/apps/mobile/ios/Spacedrive/Images.xcassets/Contents.json new file mode 100644 index 000000000..ed285c2e5 --- /dev/null +++ b/apps/mobile/ios/Spacedrive/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "expo" + } +} diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreen.imageset/Contents.json b/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreen.imageset/Contents.json new file mode 100644 index 000000000..3cf848977 --- /dev/null +++ b/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreen.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "image.png", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreen.imageset/image.png b/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreen.imageset/image.png new file mode 100644 index 0000000000000000000000000000000000000000..0e07622120d1caf7c379eeb268829a8ff685af85 GIT binary patch literal 79413 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreenBackground.imageset/Contents.json b/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreenBackground.imageset/Contents.json new file mode 100644 index 000000000..3cf848977 --- /dev/null +++ b/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreenBackground.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "image.png", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreenBackground.imageset/image.png b/apps/mobile/ios/Spacedrive/Images.xcassets/SplashScreenBackground.imageset/image.png new file mode 100644 index 0000000000000000000000000000000000000000..837b3d577871c05e66b87f3ef8ab5b8d233fa137 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blZci7-kP60RhNk}vj4X@{|JlE~ Q0EHPmUHx3vIVCg!0CIf|FaQ7m literal 0 HcmV?d00001 diff --git a/apps/mobile/ios/Spacedrive/Info.plist b/apps/mobile/ios/Spacedrive/Info.plist new file mode 100644 index 000000000..05ff2178f --- /dev/null +++ b/apps/mobile/ios/Spacedrive/Info.plist @@ -0,0 +1,77 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Spacedrive + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 0.0.1 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + spacedrive + com.spacedrive.app + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + UILaunchStoryboardName + SplashScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Automatic + UIViewControllerBasedStatusBarAppearance + + + \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/Spacedrive.entitlements b/apps/mobile/ios/Spacedrive/Spacedrive.entitlements new file mode 100644 index 000000000..018a6e20c --- /dev/null +++ b/apps/mobile/ios/Spacedrive/Spacedrive.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/SplashScreen.storyboard b/apps/mobile/ios/Spacedrive/SplashScreen.storyboard new file mode 100644 index 000000000..ed03a5299 --- /dev/null +++ b/apps/mobile/ios/Spacedrive/SplashScreen.storyboard @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/Supporting/Expo.plist b/apps/mobile/ios/Spacedrive/Supporting/Expo.plist new file mode 100644 index 000000000..3700426dd --- /dev/null +++ b/apps/mobile/ios/Spacedrive/Supporting/Expo.plist @@ -0,0 +1,16 @@ + + + + + EXUpdatesCheckOnLaunch + ALWAYS + EXUpdatesEnabled + + EXUpdatesLaunchWaitMs + 0 + EXUpdatesSDKVersion + 46.0.0 + EXUpdatesURL + https://exp.host/@utkudev/spacedrive + + \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/main.m b/apps/mobile/ios/Spacedrive/main.m new file mode 100644 index 000000000..25181b6cc --- /dev/null +++ b/apps/mobile/ios/Spacedrive/main.m @@ -0,0 +1,10 @@ +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} + diff --git a/apps/mobile/ios/spacedrive.xcworkspace/contents.xcworkspacedata b/apps/mobile/ios/spacedrive.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..64dde6616 --- /dev/null +++ b/apps/mobile/ios/spacedrive.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/apps/mobile/ios/spacedrive.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/mobile/ios/spacedrive.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/apps/mobile/ios/spacedrive.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js new file mode 100644 index 000000000..c7768fd74 --- /dev/null +++ b/apps/mobile/metro.config.js @@ -0,0 +1,64 @@ +const { makeMetroConfig, resolveUniqueModule, exclusionList } = require('@rnx-kit/metro-config'); +const MetroSymlinksResolver = require('@rnx-kit/metro-resolver-symlinks'); + +// Might not need these anymore. +const [SDAssetsPath, SDAssetsPathExclude] = resolveUniqueModule('@sd/assets', '.'); +const [SDCorePath, SDCorePathExclude] = resolveUniqueModule('@sd/core', '.'); +const [babelRuntimePath, babelRuntimeExclude] = resolveUniqueModule('@babel/runtime'); +const [reactPath, reactExclude] = resolveUniqueModule('react'); + +// Needed for transforming svgs from @sd/assets +const [reactSVGPath, reactSVGExclude] = resolveUniqueModule('react-native-svg'); + +const { getDefaultConfig } = require('expo/metro-config'); +const expoDefaultConfig = getDefaultConfig(__dirname); + +const metroConfig = makeMetroConfig({ + projectRoot: __dirname, + resolver: { + ...expoDefaultConfig.resolver, + resolveRequest: MetroSymlinksResolver(), + extraNodeModules: { + '@babel/runtime': babelRuntimePath, + '@sd/assets': SDAssetsPath, + '@sd/core': SDCorePath, + 'react': reactPath, + 'react-native-svg': reactSVGPath + }, + + blockList: exclusionList([ + babelRuntimeExclude, + SDAssetsPathExclude, + SDCorePathExclude, + reactExclude, + reactSVGExclude + ]), + sourceExts: [...expoDefaultConfig.resolver.sourceExts, 'svg'], + assetExts: expoDefaultConfig.resolver.assetExts.filter((ext) => ext !== 'svg') + }, + transformer: { + // Metro default is "uglify-es" but terser should be faster and has better defaults. + minifierPath: 'metro-minify-terser', + minifierConfig: { + compress: { + drop_console: true, + // Sometimes improves performance? + reduce_funcs: false + }, + format: { + ascii_only: true, + wrap_iife: true, + quote_style: 3 + } + }, + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true + } + }), + babelTransformerPath: require.resolve('react-native-svg-transformer') + } +}); + +module.exports = metroConfig; diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 3d4df4bf4..bd510e6c2 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,6 +1,58 @@ { "name": "mobile", - "version": "0.0.0", + "version": "1.0.0", "main": "index.js", - "license": "GPL-3.0-only" + "license": "GPL-3.0-only", + "scripts": { + "dev": "expo start --dev-client", + "android": "expo run:android", + "ios": "expo run:ios" + }, + "dependencies": { + "@expo/vector-icons": "^13.0.0", + "@gorhom/bottom-sheet": "^4.4.3", + "@react-native-async-storage/async-storage": "^1.17.7", + "@react-navigation/bottom-tabs": "^6.3.2", + "@react-navigation/drawer": "^6.4.3", + "@react-navigation/native": "^6.0.11", + "@react-navigation/native-stack": "^6.7.0", + "@sd/assets": "file:../../packages/assets", + "@sd/core": "file:../../core", + "byte-size": "^8.1.0", + "class-variance-authority": "^0.2.3", + "expo": "~46.0.2", + "expo-font": "^10.2.0", + "expo-linking": "^3.2.2", + "expo-splash-screen": "~0.16.1", + "expo-status-bar": "~1.4.0", + "intl": "^1.2.5", + "moti": "^0.18.0", + "phosphor-react-native": "^1.1.2", + "react": "18.2.0", + "react-native": "0.69.3", + "react-native-gesture-handler": "^2.5.0", + "react-native-heroicons": "^2.2.0", + "react-native-reanimated": "^2.9.1", + "react-native-safe-area-context": "^4.3.1", + "react-native-screens": "^3.15.0", + "react-native-svg": "^12.3.0", + "twrnc": "^3.4.0", + "use-count-up": "^3.0.1", + "zustand": "^4.0.0" + }, + "devDependencies": { + "@babel/core": "^7.12.9", + "@babel/runtime": "^7.18.9", + "@rnx-kit/metro-config": "^1.2.36", + "@rnx-kit/metro-resolver-symlinks": "^0.1.21", + "@types/react": "~18.0.15", + "@types/react-native": "~0.69.5", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-native": "^4.0.0", + "metro-minify-terser": "^0.72.0", + "react-native-svg": "^12.4.3", + "react-native-svg-transformer": "^1.0.0" + }, + "private": true } diff --git a/apps/mobile/pnpm-lock.yaml b/apps/mobile/pnpm-lock.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6e200d20ad7957ded7182609a6a99adb603944b5 GIT binary patch literal 304815 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%0DxZ zXwO^M(rw9w;>ctw?fW#9T16$jRt_}BmW2LJ>1wl8E_0AA*fXbgXavH|Ql zlNxFafKKUeR8U+ValX$<1hwlXYUvsk;kx^W6y{)wl!wN)X1jw>Z;JWW=#sc>BBjH?`Zj5ef5g1k8{E`kkiw6 zjtTg7+EusfD82GTi_kN01Y`qfF~Ki(?5S~0i{(<{y9w<}e&tZqe7_1kfZrgDBQ^+&Wl)B81P=dFcP~`S1&V-33>bwJAian z%JVgyGnkd>g=czeO^zd@eNFGzp=EXtD|ByieNQ7y=jlFfW~L#GFW#cznI+ZM^SR@r z(awr^bCNk179sZgl0cctJkI3Q@R~!F5zxm&+>}5UPkTU@v$o?!w0APvz;CPX^w(e7 zc5d~`%RV!a!!$oq5PhKlu<=-Z*sd@9f490#=UaLzc%?0hv70a)YKmT0Y)r?C!-SCP zHGmorJ9N7>pg^oZubUj7FA_b}5>aoj8LUlo)MnEuRUq@*LBb}8!db7-*H^9=Bv2oW z0DVfQ&nto<@;LB+mY9Z;d|;NlBd$M9p>G%9{mZI_0&`$0FY3`*pEI&h$ayy1Lm{rH zlk4&Fd|DT+fFCX+wX{hTj?vMavB|bU))IS|EIp2`iHWH$IDg4lC(kq7&Gke5|IaMI z=dAO`0^IZU!&UdwLc;z0ZtzXqFe)8k?O3cVpKcxFEIGTeecpO3ndLi-v@--Z%abQ@ zrISngj@F_v3b+dxJ=x*yBG|~Gbe=8t{OGp680{SM(+`NUk3AgX@b76nu57dJxI>yl zauXpApcx7j_#-itq;Rz^dB>Z+v9(Ux;KI(~g$R0pE=dr!?usvtN8^yRh)RSTu+h*> zis63H&JN#Nj(>a?9r$w*{Y~f|brf(%!@+#ZuCx8N*Om|*4B4|rVg`gh#DM5j*M(&A zI$Tz&jjPAShIL4wvf0I}LQ=bMJA%?!htOALU~sB8*q3+Hk!#EOr{={4b=vAXJezKVc(@%>1_sa=(>XY8m@OZwgfcb5A z?+eg7O7DsBZA{pc1P4qwAy~4tXNcq30*_TkqBr4ea%)918IKQp;xu=`0X^;V<9N&q zKp)l81>#OG*W%Eli%e6AJZ?0%Sd}dDa)ACsnf$Z>gW~(prf+H5yQ1m$-0eY;OW+9U z-IVvHeAu=rxd{|4Lk^my?2wDnFlxaO}nd$vK%SIBiDI#Y8tGW@o}*6tNmrRCnJulku@f(A71Y0iZiRk_CI8HrvfoJb5`k_d;CJxYQYy>e55XrXE!m5E#jftE)So zO4p1-iQV{Hopi_b7}jurR{7|{p1oP6fHdtSJg<0Nk~2$h_?vPy92DS}w}Aa#&lb1k zygVSg#iWfyZLf+IqPjRMUT`Yd;g>zxjk47>*hq6pnea#+$P^EFXCL9H-<1uJL=CMh z5OWxOO@M2-Q`bU`c$N%~TA!1AOD@&%Rb?Y5PaN(@NNHmj%2>L}lg2Z1h6{PU%<&s9 z(0Jr1vm!}NJ`i{Mc23D6bub!s)J~lfrEi5Nx3Cs~d<>cV2c-b+uhVW&03wId5=jAiK z4vA%YMb?Mdy3F~1QZ5UFAHW%Luo~V|(W4BZ4Ia7~GW*0sum@vN=FE*V|9aAO%>iuO zv#O##Kmd~lWpQaxRB?F`+QMj-9u$3w2S1nrKDW9k5_fx=PP(@h+ z$s9nicx%O%)ktMBGirTb$!;SV74ba-_sTlOu(SZxm_pjB7~x9+z^tIBhm?r`J2-~m zNCmsuW<^&n0j@^StI~>YAUv?k+03FyPr9K(2-mB2r$8#NPb?YVt1@MjKB0_21 z`aJJ&V{dTDL?Ng884r=$eli!9dMAgFZ`du$8!?@YN9f7l(2+uNHNz36E{MBjWEb4G zYJM1~S3Za#rjG>fFo~zN|2{!AIM(gSyGWCRs)u@}MYacoo%$^bB4{z)Z_hO!VCiAH zDw*)gVCnj5Tq-q#GzOP&NjpM2jg;nXkQ{a#l*8-pB2>S{av7qFcUrWsl>C?NzmNdz z@t1?T?_KJxhC57?3)^{uaH<8}tPrSt*9bl^DVouNm#xrhmAc!R$f7p})u;5)*|UfM zjqv0?bZ+yBC-V-DtQ(dvd^;jX;|viG?#f;5N87O3oq?)EF4x`!%#NVzajryXMGIg-yir^&%Tu}sTum=|M5rv zsJf=7EO}*K0Tbaqwr3m!G}Ch?t0+W`8`;ogWb?4 zBOfg-_Zi+>poW(ro*tJjpzM8q`y&Sr>8>2Y;r%l}D|(LNiv<0Bz3vbG4LmuaKeXM! z%pSjghZp~}d%%zB59N}6oLh2TR{;l!1eY2aZBRL$ad(VCq~$7-U7&Jw5EkclSou@hWbwd)P# z-YAiun}WiReRSnZXH016*$M(fN`85T5x%g!{z8F#cSHux$Gf!sEF8U<52yV8^8JMT zxSeX#F8I}7?xd5us*I7kj<~hS zHvvhbT6d}}CQcIu3eUWt+~3k*l>|xx@JPn_wZ~s9O`j12NgaKS+&8&7 z6WxYuON>akm3vJ`09t2m*O{Yn;(lR{*!@`r;uSYOT>!Y$(Yj}x zjzW2I_S%-iRugl<$%^a6iXJp#yyEn;i_?J3z1(}*q_}_s{j!g~Sj%U!?9+PQRmg+# z0OF-@yN`8l@77a;x^+{ofbx6gwzhODQmS#IcNBW0V&^o8Qs`NgZZi&ZeT}8A6y$?1 zHM25ZPl^G=Yo-||q1n$pAzmMxZ|nFz6+HY@6y^dI3gG4BN#g@{Dgde3^^YLS0K$6q zb}?(l+W4xzk#ZNH-OS(d`}#7ai*+%dF+i#5Ef8z6i>TVduoVRe*@tGVjAW|xS9=nA z#f&M~)Y6LN=XRMu4Q8<09!cc8jd)Gw&uXymv-)p;{NH`(?@m5D?9ISG+lR~Uul2CF z_x~IE@rS2F80g3SBLDTc|Gre``~BKij95tp&If48x10~sVkB>}X7W&tY?eyXE8t#* zDlTxJxhe=oof7%BKQJ3-q^7DtC%!{wRG&s`_OoU|yL zxYMC()8Lazd&$oi%J1oP|MgD&!%ey~nuljQX`rHUz+iZ>0u0YSnR7iQ`oJ~Vd(%&& z+j(S)dqWp2&|%GcZkp$=_do?7?~bbOhgH=B^v3XO;Fdo=GL$V?fJJa0#Pj8cO$x!N za!6(NhV#=zd_41j?(p@z59R;z91JdCbR3M}SL^BHqXXgIV8(uYYhR%{fGPB^&-;MP zV}y@@<=vzogPq^iFB|^ZTt84XJ>LJDrJMHC1GnK0_i(r2e^na2Yh?`@;yv5OQgiq?&MFSf9CDpO-aUjWfw*~6BjwObDCo(_8CDBu@MnQK$0%d2X$RcQ3)cV?m^uH zr}E@*RXZ!$OarUrJb#hM~JM24U09p!e&ZlWvTRxa=?}iJl)n*Qu z!l?y8AsC#Mp!AtxX5sDvCK#i3n^IbhvhjR_-!LC)-xlp;jWJN=Bh1)zjZ%9&_H}k7 zFy^80XwO+BTU+{Wu;3lMtl$fr&L8064Ng3`YD3=lJ@F1lsBt>)Z=R*217SOKW}7DWzdS08yt5L0vJD454|l{4EL8{}KO zjn|-|%>q=_6*!~b56=_0n71|)Zu{;KHw3&B3((Sbth<{UahxAl0m)v)qJUBvG!rh< z1}ZfRN-F(#z2pRK=&@X5R=hSnlr~HOo15_Vu98 zup;EImwi4Tt3o}V*YvCwro5f9_~_huloOn@jsRRnb$M#fRw0P9%22L&neo($E!NUV z0ffs2jvzMKeCHs3)J6Vo9M~V|D|dc?r_LT^WWW~gwdBW*au0w2oq5Vm>C)NDuD4KQ*^@f1&s?BE_phh>OuS!nyFvaN#L8V4H#PQd9LZf&|# zfTHWVtkI2Qj3zT`qEWP4#E2Fno294amy8qg-285}U)tdwA^CmDzd)z^mK5mTUI(!i zSj4Qo-+IrhFwd&61AGgs4|DdcoIQC`Z$I?5`14Ie%m}I$AAz0cjb5Ft+$k-;58w69 z-ShPxyv6n1?!GPeSO0<02bb^2?egr0mFG!lgR218lS9YafAuf_XV&{4@3{*nma$kx zbKs^F(2#Zz{uc8)2&G&jAZ(xlOFqmK_jXRjaUpNlT~5hUH`9!*dz}Q{k`5+?AIH4E zzb;IYUin*!gI1Jhvh+`FIvC|g>-1L@S0Nq(4@{r&<{8iWnc zb#JEi9pBT>Q+k+SgCO)Y;Sg4;YPVM&gHrk8t>O7jXa&;REBt407JeKQNuS0Ye&g}I zLwV-|aSp2G?<(Ud_fSP}&)D=Y1G39~Ki(1Lez=cc`0-0K{qXWvd-__2(eJD9mZm}# z$eMHYUUd(+52S!}Wg^sJ3kdBN4TOHorpYF*t;(3^TLe2_DTW_S)WiJB=4x7qYOQ;V zQ1-F_AR`Ifx3?4N|R|FtLK;%8gCKfa$%CER%ZP5-x-FIn3XClgn} z(BrcXU;}Duug}Uz&E*pVbj}hH#OIA0dBv24dO(2q+0ZO>s~;c{Lg8}}ZFVjzw`-t*YAi+Zn?0*{xhB|!6P{LjK4^|%4XN;Yv0r-u%wK*e+ zl$QY3j7(49$OPR+66#tA(1!4Xh#zGJ3CR5oG^S^8Q5=IY)`0;*L0{Sgq}6G^x5944FVB0aEDn=EMoh zB9oVM=7t+bm91&_uLF65ntY56*%E(ZAQWJKfIao$sr-L?{im7JBz?_6Q$vgQ zeNP5g;649(-$AxW#En8J+kCz4d^$q+i^T-s;`fT+P3*8-NXoTcYZ80gO(ow{Jt|sm zX_MvFT$ysE+oWt~5<)7wfFF<7(`EK@Q+|pO^_Rg{J2P-m-<`duD*$jLch-(RWDNT0 zp)s38KNC{90P@44kimwFuxq!U^-KP>DxcS!I zJqdw=#OnYG=n!Y#lz}>2bQ+muf@qs7;o#y`6E;%>vUF-e5-qoOd{TFlM4dq2&Sj4t zfyQ)mJ0{Z!VBb_7dz+Wahdce{LIFt2XF`bHvH>69u{d9f=T{T1fU&O%egGJ?>z?}= z{}XROnKGI_7q>G-MC#Mr;QfwNcaVKt<5NEh2=? ziOd8}a+yx{+WJr&5Zy2fYkY6|?8W4tUjRQ{y$ygo|1?`T`U6_bW62(G2E_hTZzA<#xg6al%vw=RmujGF(8gvOr z!Y7LDDgv}=)KKXvqYisa&roNzYL|zIo{qS%G0BZ}%R8Io#1SQV>lu)g>oY) zcYirzXNdh7U&b%8uVXJ`gZ-{ zWhJDH;1LGTp8DulpZ4irEx#bN9~SdxPxT(h_{rBACi#vAd&+;mLU&-lUXAr(np0Rh zkF^55_8E1%f5$s6br0qBj5R`-753&Wt#DhM)jH{R85J&}p}#ny){#0uro%WKLaS<6 zR3NtQ{BYV4Sx6( z`8*WA%)dVyule`3+pxLs#n5-@36qVO*;EZ(qO-+%5AC%Xrt!Rga${kxg{JQ@@b~4E zIidJ*fn`hN6xe`nw#Mv)aMH?mdhr5B72@Q5ZX2pi0Zh8rO7O|Yv;Y6X>w_KM z1K9n52$Pizm3Z$N&EB+-$xObI5lRKLJK{-wK~CCDD$t9CHu9I^S=doWZXQ&vh|?P( z8k4*|=a0J^X>%(C>5_c8SoupUto3R9EtQ?4RyH@4Ksw_+J6T=jX*`#*us7zipAsA}8F zm}>8s`G;Nfh0OJr>vs)C|N2jVOC;e5IM@H(n|fd$^dj<1nEmv;wUW5EA3#i=yi@&| z>%-9?MZJUcQPMl052F2>Ke|7Ge*}st?o}9l?bdwIT>U~8U|aTP$y-|a2$;v(Ss*e1 za+(CGySH&ot41z8x z?aV3h6WcD2sD(h3}Wcb)gnb;FennjXmj3Zt9_QkAb=hx4{4o75UM#kgpdRskNB~?}| z&U-oc+0S)#(XQGEUYqP7Ku4)vE@E5E4NIot4ok#bY&6HrqC18I>6FLEml>5oG$FN zpQ;>`8upQq@qPqj1jNLIX|A*!S~Cw>pZ;NOzdnP98S+`C1BU?EHr7{UXD&Bwp- z{O4=)Q4zZV-_2;n;I|&1^lyvJ$X)|=W8gr6A}^?@m!6ADxHLJ4#EDkgjn>{QT?3j? zXP;hmN^iG80<<+OKU0GFG#|QImde_OZlE(GKDm0=swT2u5aPwjuwZ=+NNE8~3h8^+ z>t~?}<9MVc@!daY{M77xuR>~s4ORD6`%$!T=ZITcK2lzlxj?x%cB!up}+NOzjIT-=Syp#lLkF@*IDl# zO75C4w*#FOsXqz?WSZW-^(^*34(5~k`#7%G8J-HjhFv3$a7q|-Z(A5`X-&%SI`t4S z2_nn6i}`H5xeeDnnE(aa#EC9SjY`fDJwQJcDq>l2ReUPE5s|sqcj!scz9j?v4*Tcl zy^UGPH`N}-W48pntI?XisM{B}vp+M^GuaJ2GzGERmDbE}$=p;AWzw-J{2Y~P{UoB+b4uKDmvClFf zsi4b&$#4#*cdm$5*Ud_r2YVIJr#@I8zPOyxv;5BG9a-O?$M|L3*r!Vt7$vYgtf=`mLx(c<;X#%akAU($y63Xbm|qu5)#g8BHz7axlET^8#658TID&k}O7=Vs7902Pf!lH1t65*~?H7npIDr%}HoX*9*O{*?t8R8xtUmrOi}Ky};|!X%T<{mFceu?T zcXe+ScuB5-O{xVFy2&LzAJHaSnDb!4g*jVaWVkkfQav6w7vPrSMB6T~=`w9epetLQ z8NsS%MukZGC9y83vgUG5X94#q<&^k63i;wvztcFMcJ*VO>XT&OU&bTmdepS;t+KDl zFu9l<&g!g<_j}e?2eu^3sGx#jm^Nn?8B&C7`+VhM-uc)u2}~h)n{stHc1g%6M{aIL zYmJ&&no>4mb=%3IPNbXkyGwi@2>L2M__W_K^ZzceBS7o(CI7VF-UOJ#HoIMcmJf*S zZdq2{L$}usV8#il6;&8Nlt(WO(m`|jH#P;o)S_Gr9)^W-Gmw0Pj)J%%{XIqTM5ARs_qozu(sPtKb6bC znC&Jgsd@bDbQ~@pk?0rV`^1I4zqX$`B;XoA!MybBi1ndiC!E%+m=TLEsoN>Owk8-8 zgg{nc^B1LI2@{!yLbgxQFwHY^HgkK<=A(qIR`^-8{pD5JQnffqy`^gm+m-h&hd*ol z?DkhUqFBC~OeI%sa=uAXzekv$6M|%e$Egm)0@cjqTw~Sp!EwmwlcQ==aX!?o&KehO zXI5-upi(~^PL0DY4Yp+5>N>ZxUhwSnT}Zw>)whK4<5+LWof+(|oL>u8w@F~VpXV1VzLS0Z&U3oA#9zne>ASeVrp7-|u(tp%5;FL2t=r>;JTp*tV)#4f z>BDr*A5r1YH~A^m2`>dQoiEATMxd9LPC+Lqy$B6?yU#Z5)S5%`rNT)kG%jmtvfTrS z701kkh`%{W&^5Q6Rp+VJmgC8d}+`sd>;yXPJsPrsw)`V zwMH3!rbxc4y+L!q+g(F+e4idHJgy* zDqm#C)>fr>s)#b9odsUeb@VcLNlLDG(ME)v8SH^#VN}s}BI{O(bDNN{E$*lnGzu9Y z((KFC2QS@M_w&ms{IUzE3;+w*`}7P*$4d{HCo^Xbb6ogx=TAYgCH)LXwx){)lI`<$ zBF-nPHE|)s=}emXW)aHWqC6>iX;BC*i~EIqpl`u&C^fHN6x5Q9clEckJ|F(tXM+u; z_5c(RFf6~c?qP=}$BQsq+*WG|#wayxqXg=PJCd?BEXi!JP82se)4;lVEqtMe0pmUv z+Vi$EcF9%CF4xqg95mO?8Zg6L)C1*4i|DlFUw}7u5v*zB88quuyI$(7c z-EJg+j3OGGBj;tG*%SM`wD{Op8B&;JYi(6sV?EBP;}+$jcFqB&mvpp+YwRcolN`=H zNg0#V`*OdMnBQZhjgj7?yZ+-*{o^0k*o1kLamw^9ZSvO21(OS}I!}ENAN?hfq|)xh zOQn2Tm;TN6_>|s&G#s=TgM^6TL?;azhc5h!0BQ&Q;oTd@e84z;VKj+<{NLoL8UOo#en{1?`Po-a;^7lN>?%|m$JuF69Aj=4bOPih?taAc zL|`W3#n+W5tBZ1-H2|c87E`%>6?`PYm@2{bzbnZ z3PZe&z}{W4#{+lAsoC+|p~v7QGt`BJaFnrb>2B4UrfNMwytQm|lF!pZD8VGPdj@Zi zj7nc)ikWrQdcT@Zv!#-?G;obnxwJLSgUX-BlDU;w)6jihQT&W|KIz;|864X=|}xABk}|c`wQZV2LZ@y{!q_l&2j)D2C7^y zE-}9F6h`0SWuxqCGqeuf`M6u}6I!y=nz>B{Z^nm*EZ+!}7_K9LO=2w1G|_cHSU2}b zEJaP_ruR(q{aWf2*d?;`2ULU+#tg&;vbrzbQZ16ngdQ~hm@=pJB-9tt#We}CK@hYi z6Z{0&9oAN^99FYqptVKT!XaiL4||;klfK;a8Csc*uv@XkZh74~vN>x$h*`gS9T2B| zlL`L~&fDj9mUrZZ=TzJeAa|qk9#QV@7YrRMZzoj#6#(qVU*qZAbKW4w_n)}Gz4xj6 zuYMX16Mp!m!vty<`gHD9u23nmD$ovH3Krteh`ELD)_r-oPCKmV#5JG^9HpSza%V z!0wM19QRXwE*=p$nAH*zPDSh^+RBQ^VJKDtt%<>4Z!QtPEBZ)bE|<-*r3Z`v)7$16Vs;OlAJt__1nBR9o``?` zw^4sA9{=;-|8tD_{@VjYYuo~$B|#0b4DI_%drcqiyksu*Ot%dvcGuhJqS#!KUE*DH z2?O4^^EGu@rH({OV9&P%%UIq4>t(02cB~UubU!gR+vxmWbNb59 zeM<9PXN~Uy{`2-MpuhOxJ@xd(dWxY;pyg8a~{Ie`ub_O94>PvCki(mtCk z?X=yeMs0|;5-IKUn)xz~=z%kX$bVg6^epI=b*HR#T}iQoqX&Op%0R`wzSx+L`_UoW zef}?{G%o#-{<&A;UkgSyf>~j!9X=ywSFT65ZnFof%(xaK+HaBwo6ULixFjK~ zt1Wpg6yWNdRxj$F-pI+EQZXe~(&&qoIv3qXhu0@EK zH1s~5o{ntXZD+KZ3-x?+s63LXFJdbj9_qJDeJHGcL*(>A_mXu!6n2kw)zVxFryv!J zNZ{3wIT#x>6V&-d=@utce1C*r+%H%n@MhmYQeef<*YXD!M)q#C(+f+XN-Lc zQjMhP#;X)eT}Aa|HoHM(Xm?Nr-|~{?TZp4E-w0aZn}_t%-p1qkDX6%&I*)sNtyN6m z4ObTXi?P%+Z8OUZcW>&UJF8oFi%k=1X|!D1^#@B;&RDR$X%}VK`%^i?4O}MA{FOT+ zj1B$Ivs>Y^yRF$j|)LU2R z7)3pMBYuSTACF+{eSqQ~91b8LXL0@P0(%(29}Z~z(Jy@d`+%YRwopp=X#M*&eFGi1&q(ocY|os<|H;D~o3O zoEiq8J=XnN%lE zO#%M@OMCX&$YI6#?a;M~wV{axsAxKE9Q73o>dePNuFIl*B zJLsa{d$dDr2846@BLxj;eUNP_%AeuF9Fjw(wuL}*(B38O3&lDb@#kVK;AI?L$=8+| z1>D(`hJIFB3!oyg&07s#-b{^>l-A0{vJdQjF~239xLXuxt@F$>nltI{g7Eo9IWx7p zJlNB4ieI%x9-a>d`dHp4d4C8Ve>30*e2YwEmDmwzodc)X< zJZZ4Yv|p_-=W7`iUABueN*XjbdJ8fFxM~L`|qlTZ<_!-*pVILktuq3XZNHL{1wFE z`B%8t=U>6{-v0`qAmGV8{(7&%J^nh{)MvKxR}KCCk3NnL&UgItcO|R0-qx zoNN@U4%Aj2QZd(1WX)y8yyLGCl+%udhc#0scMjHCYu8sj>m&im5L99)LusOwphjM@ zp^hrz+l}w5tqx3APvi52N*E*J?%gH%Dj#(5N5$hP)@2UI8P<9SOhwujZ_y90je-1oKKEW6>qLl&rPs_?&`IV3X^3$evNO9@ms^#E zzE>&ROpNX{Up29Cct6T7gkj8hkGf-A>sVWMZ!j+UK}r870QdyTYI|dog)5rf9{Xa0 zwN%H{2I=lrt(fd&y+yUl0^rCUi`3Wl5+%KGrMZjjbnK{LdD>y_wZ0W~ZL6EM?4{Xx zPH^m&=+~tn^^++)m)D@E|QCDn=2EK8dggMq4UKtWpMJN@x}YG9P4FIX@>X8^_@dSn0~qI0l$PEf|H~@BLNGQO@g}D0GPT;_mg6AI;p+l!@#a>Aq&QvWtG3rSdwM8ByM*3 zf>_DqeqDO=?>73dG4Go43mfswV?2VKXXx|3IS78>Gv0%ruzbwHJRO+ly+00O)VHtQ zI7b6m>_#p37Y8~6nhM#-^lhzZz%Wotryjex({lv;LObPj%v1HI%h;r+=E=D#Jy5M_Tga|57E&R-*l z>D_ugjl>_QQxA0-X{wzF$&E<)ssqZ88${M1ETeUfPc;6J;}r~Va2mGTZVi4mp*w3W zSKg!ym#Ctv?v%3OVQbX|Z10M*Xa)P;u>r;OZL;OdwEF!f-5V59U~~;!pEvBD*PbG2 z3a8kJOdxCAUzWB(P$HeSyQPd9(go&f;%J%)c{g2{i|x>DC1?_Kd1mnP;N}r9mwJY7 zLTPq9Hd=Y)T|&KHAZv1eia#!EOoork0+012lYm~-&9Gyxfdid-S}o=`Jvyh2+O(5- z2%|oWDVEt}e@&ZXF7g@D>^4SHHTyl|1G}F$mD+(f#pU2~LQc+xqb0^u>gQG6r&Df! zuqw^N7ZGjPCW;*Cy-WopO#<1#B3lork3);}3>dm<17~4A;I{PW1W;lkCt}q_bd#n4 z2)e^2fg<0I+hWVXn4D_vPwczof8`&*S>CVpetx4ah68*E^5Yxe@Z7-z@1rs!Mh0+q zgA#r*Yh~$fZPNa-?&_-Pn;o3N`+mMqKHqgGlv#a)M)y0a{x}u+m|Xs7WxT)J#Y@t| zNO^KwqoU+vDy^S(_(|(c=7cU6i*_YkPX`_g!~zDuimlbD6iQo-7iCH|T$db|5a%GY zH}P&8tN~9f=F*!Jw5N|Qv>%dpcM;E^(-L}jp5!ItbW>=T;NHL5tuXO7Vbg_+-GSG| zWTHs@029aKp%;&UZT_xTQQOwXjkaKo(( ze>PP-`rR6SC038l)NriBLHp3yTQ(ZCUp59Eq|RS{IMy7!>>jVYcK+r$Puffhz^J!? zyb(t@nWcEX|W3U1qrp5mf;pI-ryW1X);Ia9*?usX!TEtqcg$+?WKJ6!iu zCcNe1>3u_A$kn%r7a)Oqwjlo)FneimF|I2jbjgM01)^8Hbr{dzdB%0dTva6&H}2U|Zp zp`S(OhhrR*=C3LHb=BTB6St-Za|InBav}L~Pj03~6Ncw(r>D(oiyE^$rk%5I^-(j& z@~sMEl0)Whg`Vx_bA;7I;hY*vyGi@@s-9LxmT*hEid|AZpIz6H z^V^WLz>?~o&kTP$L;-=zJPu-bn%Wc5yXWMqqp)+e$K%!tVxqTPyz59+2ZSiY3 zQ3A%-?VYr5OmhBEJpDio4qewJaSSv%{K!i$&9VC;V8Pj7Fd{x1wpK7XkE|;}hO#gx zh{S~T@Tf$GtUi^+hUAEH4Y2kcgOL1%M=9PpOZ%-;Q^OuB*J-95)53gDJ3ex%P+f$( zdTBXK()pK7xH>TVz}u(!A)g7EoP=Jxf*!NT-kv*&jv6w#P=t-Rx?-2ofwE^a)adt) zz741AZX0n3R%-!&$tS+;$jP@7`%%jNq_?0HJeivGQ8_w27iWTGPdenNsjRUVlk;jpp&ZBFcGEzZE%C;N4ha^oSkJpE2Rvp> zj{8n-K73k;kv=n}0I2!cAMf4!%1GEEuNVlHW(9c#s11DP%#fW zMOg_5npgLepw8n)3O`R@0gx^v``JLwdIR9Uz{;jEK zEciXb=x1ghHZ{_E!|uizkdJ1HPn#b7gx935aW9wPXd>+FNb^J51gC9hEj+V}Z|y1V zN|#+)%N)Z=7~b8BVTM|g!l%)N4TK1}gv)}MbvI!X9IsReFwyvW@Q=cGX7xK;^MKKL z!)$z*gr}DKyzNKu_3mH1=Rw1yzQKP%BE|uOm*eH+EEq&vH*B&Gx2q(k`s+|gMBJ{e z%$sgD7|uknc$8T6b;fFRx@zUsv7v`Dr*~Io zWyt_T)179qA6#s61Ar;A9$FE5jti2c&a+96ckOrL`eEKatBZ8h&%gi$VZvy{U$ZkD z6GzB-fJQKf3~yV!kb7^JrIO>RLk;j#fIDU!bg!F=Q)IGK4aTa~LxJYEb&R7#eY+kV zYN`VUEEmjJk3OP-F#N={{)26JtHs~f_geA$3d(6>O&0~$HtW&`2#}`m$}Mw?Zfmj+ z&*ib^pn|s|#RaO*5ff@a^~s?vFVO9NvNq)T1mk=!7JPm$mCb>@ZJ6D=Onl`(|61*D zp-h#(ksmzf5}vNty+i0V#oSG&oGhNjo|s4N=DJIUL+EF{vIW7-#d_c^T1o~U@d2&F z$d6QulpP}?nnKSblY~UOi%JKy$h}UDT+!~MM2D`4XCm|`U+vTSUK51tq}iBf45rA< zxjys)(?C1q`QmchrjYn5Eoi46)NMZGM`Ku)S6+Y)LLOPAbf^f_uu(E@?F#vs?@SDs zpbYLp%0h=>BwY~7($6L(%M(3OpZKJ{1Ud;+m?;;m zz4DMS0Ihf)N%y3*ZGJg~XE{&@u`@L2bF1k^6Q}aJ z-*?VUQ-KDQI3M;}y7sKW)Epd!cmv-@{v9&jFH0_?dHTmcz!p6$*?XV_QO`*2lcw$) zvH7D7=>C*GELmE`a3JW5ZSOaJ%G-ynORs6WC|5UL$r~fRNr^5+$SPs4{SGHxYI@Ou zF{`lbu(6ATXXEY4g*+ImOC=A3eZh?Ol>BgX*v5{x+V3Uzjmibo5X_am#M-det2+r?4gpfj(MTJSAK^AylG{h9C(L!QI(9)tU?HkSzy1Zyanm z_#&?FwAmB`Xix=JXUkUPmL^S3cj9e-)E|J(k5hbm-LN8f+%K(Y%5xK9N_JVNsM(g> zMB;4iDb_v%P(7alz+_;E^@-TIzEdF|VLmC+%!u#AgNiEzjZQ zbdbL7of+#)A8m%8j}n^bY}+kM066$4?B1Qm*Bk_QMhmkdWi3J8%;Ok6QI5h-Pr|l_ z$289`^JQhPA(`18D!{21g#F~WAVOpl#(S?RRwcI4kyS@iv+6kKd@9XY4tRl{cJ&LH z_}f7Fy)-fQw!ircU+?msYkBF)5ebfDgI9F(B@FLZ0BLBEJLKG7x6ST=(v)Se zQ5@{KY*k?Aj3+S7C#}qgHT}X9`_qL2!+cn+$UTHeSA(~|3rb(#<(PMSw7rfEkz*qi zfJMOtz8d}ma0{U{-|#wdJD3;4-Rswt4QbGj`!3fC?fOI?O&PF$S7RDxNIp zcxGilxy26aJStn5ggGP-1_*g%ULJEJ_xJH>O|NEcT}>{SMvD!rZs(huK+Lm7RK%sb zobfZ_YRZ$ne3?*aI~}?sKAByH){0MO0!KsaTvBqo+(bI%P(5*8EUJZME_dEX)%s^= z?b+3GH@vT@@qwn8TP>JL)F+Esi0+K}Nl0<*z;DXqQ10A}=o)dp-R&@DSpqpD5z2Ly zqbe}dNFF2BSy$#I7YybhlV(HDtb(=l?tXlw#>WciTl3Asj{cDS`LLGb&!pz9zrz~+ zfo2D!5BcJj1mJNxeZ2{t(j}#G+f^1v>Y966WrHtIeDdZ@yHl84#JDl!)r3a{VCsnU zoQUFdEorH`dSk%gQpC@l8j# zJAjeATZY$C%szWD`Ry)ABh1vhJ|ZsL{;cKB(QGSizdhm59^ekuVPg*-VMn#m=WtSgzd(n)EwhUQtHKj0I$_0B6GA3TD$j2SY(+)6G_ ztC6flS95+`XCiUlA=G+)QVMTdsOAa3U;NwaA2&5Zl*5`{^^wxDZmZ7lgfl@(w?3x! z%n<&FuvLFnHyG2?rBHSfNGl-JGOC!_xJ4qrFuktLt|**d9~8513^TEc12 z3{n^D`ed1s%SM1NJFBG4;Z_PkwOe90ma4A)a=M-sySXo~<+HuLXjjKI5BT(m-&T`o z|G&YY!w%gK%?4m0FS#q!-c6~M^W)9xq?<;I`v*4C{o zSZP~r*9SX9G#vDSN^wZ<^2K)!(7OOVm30Qx&m)%TtA6_$2~nCa*S9S}gyv~&Puq-L zlt2mwS(t09w$)fBD{+G9Fvj1pFeKs@-mqwwmr$`5I|=%Jg6$U1N%CMN7p7@W^|yGv zPnQiI^GBU&yrp0L=?94O-R#3YUkrp01-7!oe(ctYs0rTH#p1pbDdM$i9#E-JsqvHqO6KwK6#dUv1FBuc^h-j7u)n}qZ zRYa``(Uv@-lmR|zSRE2NqvxD{o$JoV5YTXQYEKghAm6G2kCoMxRn<7p7d=dfRv6&a zOhQWSp>+Owg#WX@|J=ctw|ec46pOc(;#DN3b1lT8mC;}ngep5B#d_!^w*~{iKv!A6 z3hSyA=P;$@SkjyrykN5g{qdm-mrHrb@j@9f#R&nrU!F_zOu3%ed5C9y_@{ zvC@e1@jJUSYN@xS-{pKf<=3RdoH?{h&nWR+dXv=@S#OK0=|lNHp6rh;%0|jDZ;rQU zKB!1^IppSvx%G$ib~K|izuq%K?O`0T4&>b202)txxNV5lcT0Vjl7|xg)ouECuD=9* zKNR&I^UwE+>1$Pk&W#nb-)`rG;jc(T58{vnlyvCZgI3Ok6g4=nCD$E`1+>G?t6-se zSE{*~7wnbv*2*^wK2P~C+vxpSKd40rx^ptSKkY|ZMYRL zXN5wbe!XIlf{yY=t}W+G&89ls)J`=Jj_R9&KcSNu>MDC2qqaf|qr4WD)wvU)r2vlo z6*=!bda~H9KKQP`(8)gt-AK#^_=}GO4BYUGsddv3HdA%Jo@0sXW3K9yfX;I6a4ZWA zOsuwwR{`LvxDz(J^XXJLTr{^Oqu$!5Y0BujeVIoN*~{)^zFZ~qMiJGV-n^uuz7f9h z0X@ve*HtTM4R@jD1();J;NG&kMtjX2b&4I-PWEPdF4vaGx;vd5mII)L?kL+q7nj-7*!H;Y`{MxdxK(*@g*ir?-R9dy2eKQynCn}#S)g`>sOV)DI46ALK(x|L?0x7Rqk@6@kd+VVUj4U}#=Gu{&<;OSg#4n9#fWgu77tSjin2I&k-zP&5j?(gwAvwhfR(k=P8$Q)96do9T3l z3GyDBANd0t^FSB|3{8Y+=*EeTtHHdgHiya^kJs%UDeY|Wq zy=n$(7sm8lTz@BdAJy-ly|D)r17IdOpnE_=zh;8g4(2au3+e66GU2m&;R;^G)X;H# z!(+LFp;nWfG|tp!(Za^I`za|!j9YJo+d|GTCKF7T;aPe*SuwPv>5??FK*vTQRQu7IG`OW0Cj39{A% zxdPxpOTgm2z@}&K7RrH?yGe_Po;q{CQ(w>6>(@~0M_2!@fM3h-1u(GfpRamY+4BWW z3m3ChFM__?u8p0R(nY;q;baGu9eR=P*5R7)4a7Pjde2Sz;vBAJJaP7=e-X`tyQocI zOKhl*0>&36@&#w-IsZ0_(Od6s`BtMI%e}8{?Apjq$CjND1;gn)C5?37JA*P)PZM<) zt3c{_!7ls#f^_F(c3NT-bo}oNzeluao!MF2qm<1zwi4FJm1{_J5vl68aQ1(rl>ZBo z`fvOMuL|dKBi%wVr-(+Ro%yypA@ZAl zhOXAl?a=SJBUNcKE_Y|>DZ&q@btHVJsei8=$^Zxfokvyjt#$eC#=Yhwl!b_%*m^dZ zLg!9%2Cx-Cp6`QY#3=FBcGsD}@AQREtxlBP0A}0KDUInB{@;AN>gU{PwKlkLJDIo0 zatqMG)?q6;!TT%ws>t@`W&PBU0`sddaAhX2kyme|J|2?A)h;nI>gEfxTH3m*b$whN z0ir=A7CmL^7e^y{{jBu*A{i93nyPz(33bPC8D~A`Z_6gvs&s3q4JM1ot@xOI{_e58 zMM7^Yemxcn)>*g<)tHzAx81d@wi_B+n{;S-J?`_~n&CLV4WJqu&#AhpBr4UpET84` z+9?k9$(g{Y-b~vI5|)w@Ae90_b?UR<$h+cLnZg@)isQYu|e z-=;eMMg#mw^7}CweSagbsXJ!E9O#0FIS$8$$)Ba%^-_Q}v(srMG20v%OsD$A&v|w& zKtypn*q-O(b8z?ZA9Uqkh}H|6{oDC&h-(2&`JM`S zBRJM-9c=oZEN8RCvqVlKwLhG8>o9q37>Mrc6toKI39j8d6D1w)1O}}L=|dWw%j=>G0JGr3zx!WT z-2f5nWbL28m$+x3wBgbMwGPniaVFfkw&ez4pIJ7xqp%QL>M63#i@9{d`Rh$)C_-A= z^86t0Ci$i&l7%X#^!7kG*Yi;;)d?eM-wt%VU)`I)wdL3V`Kqb$z24tl4AkGNY@ArS zz)hJIDzxc(P{awU7~WNgxr)K~$qrbkbPb$nOMb zx0`-D$M8d{a9ruVQRAh!%j1XT%>?Y$-RVTP8`>AITccJp!%h*kI&YyG=O)GY$ti_CCvxE(Et{t^ zB1M_qYCSCnz-pQFm5-==3_Xg=N0Ip<8*cYN+I?*`^0pK9ITfe;cC(dJpE^RW5=h8k zGF{74f+`bbV;6I8PIQs4X}TIjSW)aPbq|vfN*OI^#N&=IhRLip93YaH{n6#_YV)z0 zxBi>gqbNi=Ih$SVM-pwYUYD}b<+34$99xjXbhi)t%i-8 z#1rhg+2EiRb7=9^)eB@~H-5R@`~Rj^ykGG%+xlr=_o(D0$<_m!8klQMt~P2k*&dJb zfSae|>f~leV~bk49ar+|Cgpvy!>sHAxy3!DS~qkK`s1J&2!juWsN9qmuolVEFffb! z7McE&%7|PTnI0RlM=GQVO1#`sujZcFgyKv_^P_&{!XWlmX4(Q?%yh838^2m%0ua2a zDS36XvjcO1bDy7d@w8tB3s$Pr6wf7X1Km&oK7Z>f;`!ogXB zh6fl_2R5;hyXd6@CpZ4pz}77$t}Q`fmE360E*)}Khg1`cNO$NltgevQZV#8Cn#hH_>aLqySEw=l83g>^k;dVZ7!a&?XGM-3cfb4buJ5U(i zvBZ8)4f-Gd8uvT7^Hl*^&ttbBaO0o<_}Bf*6Tl0zT`0i(<(2RwBO~FH==k{)Bdx{A zTr{$~KT$NzwEqG`^9P#pA5*&j_{R_b$YXF}_MBX}U&zh%-R`ft3ZY5=(<1NR1x|4= z+)yJK=_2nJ_O^xlcYpU=BlX@dzwtH6-9P>@a?L*nqaSoePe@M|IyT0kF?+;~fBSd6 z@?h@ZboF+f;|IN235o;J#`d-g+Us-buqPBYXR^a z)cU^2jhj)OyZv6z&KEzOt`;ybdm}dTWxFQZwLzbL|CIksvPSLn(u_a@C}+x7Lf>3(ZX@ckM+F8bTw%) zk7igY-4fusCoUfnd*j5#{1U&mm+3`x;=$C_Q1iQDS%lZw?AUj##9wi2Zt}nHhyAZQ zoKYIS*C=1<3z)%)uMhR|MBKH*J1oUGS35qU@jAX!6gk9lZz^Yeb&uTLT{Q0z2DGq^ zc8GWp`9WM=3~K52(KN(w$HFR*D(9punO>AxjS5;#Ab@93HUNK@xX4=ajqUTx zzaH!TNS>w{G}Mnj9*=tK!5;I}zd7*D1K!>UJ!1lX^5Y0UG}=Rp{)tqRO&u(74YwT@ zG9!JL;*0cli0Er&WYe1JW6%{6K?n?X&UP6fzq-%iqIRvN(V z>O_2iu>9XzcrcG(;eUU!4_o(@WZtn?-?<*g?A7yVK2uZwUqI*pA=2B?JdiZNO$QRe zm)wWB5zdH!*yGiim5)2sLeZI>%9*yICY03N+`R*%%o8J$o5_aT?-7)7lWNNG%{6P= zE1Nf2y*$m><3ZYyg&A&L&UwHzjhp_|E(OOhiC{z>`alxko~_2L2cX2fv`h!)eoV5S zZ5B4RcdKGT^(hR@ms(%mqx)@H)V9t6n1u zr8pDCZC;Tx{XOmRQ)MB_U|^ISA(38Mn{>vV4Z*6GeVbe@VI6_aymYnO64dr!FIRJZ zQX2V6L(>D{PNE4j-D=2I4Cw>2R63`_Rgx7lb6PG=?p6nWU5I}^(7e8Wk%5_qlOS`i z-~dshm;3hc{^MWw?^Icy!FL|5ljB8f)k8AkhjQbQEy2HXbXjw3Rn(TCcB{`3xw;{vpBseKDyyRHiLJK_w&t; z(*##wnYnk2ymq~^vpadQy_(ynfe(e^=$A}A1qR-lIyu}VQEZFZ~LK=-u5>-@UNYBn|DS@ ziCJfuyOjczvWD8TEAx`a(>1Rwku^=3yRdf?J`mb#cLa>108?lD&OdFoK=pHAMcGc0 z%f9w+a)wv8b&udI!>h|?zva}@kl7Emi|8mT~L$Lmje_6mF5Kq9~1MCst z3^t%v{>Po5;I7iXlD_BfjN>Wr)9}M!0zdTB+XtFA2+-cY{lpM?{%#)EfM|FJ_RWC% z@h=;YKHqVEA*Oo%BJn>T3q;0$>|ek6r+)gZ6gnPZ4iVI$8AHE%nEHQ>@WCeZf&4ok z`wacY!EwMeNd4!BQLnG@9nbG=tfw4xc9NyhpKbVYSst*iKiBZmIX!;u?(^Kc`2XaW zAr>E*hu=@|yG`FE0Csi*xAfi&^OtoC9pC-u^qZ6VA`}=m_(m&#FA|Kr@^6OrtB7mV zmjC$2kFh*F#t+F%-R!~FJK9YM2;M$vbpHN=w*5yLVn)7w89Kiq9W;IsnBShMkIEg{ zlSb&6r`c|SNd$m^fOqUKkT3YR#}5Z6iTkJAZ#x`&)DEtDW4}ll{PmB|-sGFA1E~dF z0$H4w9H815@KZ{YHKtPgrcKt+BmrHOhLY8$e8_H_5^sZS#=+>kG;KRGT{rZe+*}31 zQMa*wMxp?y!DXO()AN)yL6xX>`nAS}ZB3$bTppkzycknSmzsnrzUayv=L~)*=SR*c zkDizp*$xN=mK4=GP=t1-xX4rtp6k+833xQQ?uqUBC=(N=M`KHL4zWS!^>KS%5%X_T zt9AOBo^dqXXu z?qB}J$<9*zu0CG#XR+>9&P*L4M4W|eOrdDY1vFS=_<}h%GDYd9g}&#L0&5nozT7W} zb2g{7qIc%XbxE}=EnopfdFY1ugT4RT$a~Tq#Qr&UUVtW}1&#D)M<*K#lpV{GXP4(+Eoz_b^?%meNuo6L5 z${eV%pYsTxRK{bqzvg+9ktzy|;plyXCHegwygpDh3yS?(P;O8h+jeF{6_hVVJA8E> zQm)&8+NQdrd~5UARu)BxE^=d0E`mBYmPx_3#wpPZ^k|_`+8-p{X19xE;l%GM{EH&} zvv2CthG8e5-5+%2px_~jcmH!2Mi3 z2c2ft9#`}g?Y6iQ#6>NBkjwmAo47N9zqAS5@l!{K7QBm+;C2erEh1zB_ae(nKwsLk z^yGG%ttg}1*x{z(59LBeyUT_#?b@3U1V6j*>@ZuPo`ZOoxt-2)q4=1~`zjXr%uN1~ zCi~M3f2kQd@Ykw){pU|$mobA`LT`(FO*0h3x>!t8Y$Z(xm%^*Cp= zq^+IU+2PWxP~zC zk!5}QlaI3XKOyu1sL~(P;Ga$GTemqRDL|vVBnYCUQ^(_3$PAbh&5BeLGf`E!^@OFl484Jtzl9O^Rj|Sa-ph&F2Jp2T+pJbM zDDJG$aTb;h5GjUc44s^`lI=P2)-*^B<{Fuy^0v2df*=<@Ip8zN(cR6VE*Qx&iR%@W zR2O%qsFtXJiuY|}+53%+3iEjp_gUtb*6Y|%5s4A1N?ex0Rir}~6F1oV4wbA#bG|Pd zR;>mY2kPmSM9PP}YL+ZgVVJI+%~Pb(vVF_f?mA``I>6bSWDF->yNLUGW3z=M=c~yU zb;aUcNco#%w8_P^k@YJ~k1gq$yT?Hl9z1O=Rdek+lXi^89<8BPyxV#nTu(KsZLlZk+f0+A6X&bP4qGk0ll##@7#Di4 z4UOs17oCBkbWCImo$Kbw6ek_t3_5xnY-Ay~n7TYpFg~I&f@^tP+ag@70c3RKPF9&A z=i#~GifHZg3+(85#bKy6++3sgo7vxaGWUgsF`#XG;m9!eDp3NplckK-$vm^XvO|;t z-IYt=49kdvml3+2oph4g*7S^KGWyJ7hrPZ#{C~{7YqP4_k}djI*0+kd#N47Fj}viD z6sUlRfIP(yw}Pi4Aa50Z{Y`Y}n2#}C&b6yf-1<;^?=hT?{>Yv^duGelfTx3qX-BP7tSupTG+2$9C8@MR~$@2JpHQ1<}R2GBL}K~yw@1o``vHDs4|kmuTMMyfTT zkB7xP#Z1sWfEN_GpOs=Y(PVgu2oTr#cObd#MeBpX4~Wsn$L*6I>C zwG~^LOqk5gt;%-Ksp7A-DWLx(**O;&v7JoNQRcc@4Y`;xPfY*L3Vs)_7vIv`Bz`QY z+?t2NE5z})OL?fY{5;LfcaX1f7P(^|PC9=o7K3vdN762tb6u%ykeeXX-kQR=vbyT0a-dI^&D!B zPG+6k9rj&v?4-5K87sRgtbo}xPRHU|%dG9ZXIVhKje^EzvU+`$7E9{V_Q>3MMs7bn zzVBIze>IRQYCvBAh6%CRTb6NwMAj^_Rs|?P<(R1NQgHEWb{aRxgaHCe3aVq)S~oD% ztf0Oq@5V~v9g)hjPH_wpf(wG>Y90NpT91Jj(c4*k7%<|Q z+U~~9`8egy_r~)3cJ3e2aen|aw!$UD)oHg3tA!|S1;BCzz~1iGD-Y!Oj0<*-PSa)2 zqg&&6V627M$-+sR6i|^xF6>+nHmzC67i>LgiK|C%YLIz;+13BZe17#(`^c{k&-#1* z>K9x1oZ|b{LTam?3x=WE?ht`mB~>iXn`+mg6}}Ip=R3^&?b`AhKIaX z5VDlJLUu`45^b^OENMhDHgQ&*KGHhrmE(KqA-<>~eAVx9PxU|i{XU5S>H-Nfs4l%_ zY9nl+P0*QPc5qHATrtax zo%e0xkj|7Fb(>fYlAJ&yO$yKHWi0I1oqi!BVhmjk4vtcb=BnxW>Qn0Qt+^i`AcA|H z`(qm)VnCz!Z#OX~0ujY$nSN}0&_>gBtT00x5{LpCMQ~|vxBJU3Mu6zVoMq+aGCLrl zR6B|@yErrr)SH}pz=5!Nu?C)_(m3!Q=)*lyiGO;#bwE~lODf(T!?(Ts>4au(a;sE< z2YNRemvnlI1U|KruG^VM1*e##BWbxbnW@$7au9W1&n`yVx?2X#C!{}Hq26Je6nmhc zH-L{)?MA7tOvyOa;%k|pAkWZz7W2QD;v=v4l?N)o)E?M`yQ$V;21)J#C-^!x4>0#{XK20~utE^~zUBDvj%@ieL6J60Gj~}LAuls8MqF>S;%em8BlJ1q zn_v5)etJ)!-6!$^hPm%YFufIJZz;3)XaO)IfiBXR5nKQ_M*eIspvM)oydK9#+vfJ( z`2e&At_(lFbjFOmUeEKfgW^>d0d2RMja59~u0f!VKqdlNSLB&KQ-4bV_K`Py%kSOO zXWzh0BP6sEzY!VQg^$%D)%E1h+yXyAeM+?^e#v;i}6%qpj#cXS1~`Qgs` ztfcu>SKNy2pFjE~VR*NJA1D-{o(6M$x6Wvc`K94xoV#ijdVIci`DV>6jJiP-a%`FE z!o>P;%FF#cLlrwvsq?sx4VM|X3P)H1o*x4$oP9sRC(Kw6n@v2I$mfCkzcANBc>ROZ z>Q5ZS^q``O5X~W&nK6c6mz`pcT+%!9tK$d*0f*RC*G8)f(P%RXrZrxl#+*A-6dcl?R)*h!HKv8XnUFRbx>ba;P~CIi>a&w zwT3$niD*UR4#EzP)EOC@2%@kPTn%-O@W+B)h~Zspu@&Ko{K z(igZ>%q%&X+*G)%C1vkT6~x6>*T{B3%;7~$0LS3-&iv81p*9TNE#IIPeUmVI??CrX zd}(&3?{wQ$J~{yv1H*%sT#_K1ZP#WXV!puhSsIWCb2$5Uw(0npV;;r_AK#3ML^*iG zG}vgPqMUUr)K^@g$8QrD-a+5jt8Z7`dl!e|9~tD>A0fV9Wht_cTR-m1B zPs$E;qj%+yCVIOcBgu&cZs~Qs-bZ7<(Z(j!mYFVT@kJoYIP}Lsya4)y9LZqHj$W-N zyo1h)jXoXA8ZnlPwpxpowo(szjqNn!)z-g!%7FWO(yQE|2w!{q+ho7vSa@$kT<<(8 z6~9%Z1T!@?n)lhx97C1Ay-x0%3C_ET?UL{E33>Kmr>##Qh^=U&8e`{B);NPSU{BW;#qmjFMeCFshaxNG+X)^w z!ra@QjigzzPK;5iyKGK z%$@XtRfB)@k$>*^BUpcXm*ZJ{9U(n6o8NSn<(-!8+El^(YVBnFFa zug@~4I~+F!$nESFmZgS0m%%H4D+jI@YzW`E65 zLH>O5`D(gt>__?I{@J(AovzP-TGHtD`E1|kM`An=DmE1R-CVnDuI3{0_dd}Oetxy} z#dRxdc&428JzC;A<7PO`X2+5iCt}nPrMWtphFWQ#(7dlE<>xhkByV+^;o0|zg!-Xn z3+DEHm(y(}hJf+AMflFXzW&Xx>vsPqRSSBz*$r3q{JM8+{2qP}&3qrnD`kd_mRMkE>n%{Bw&g+$4##q9^22f z+R@L&N=&4+igqn9IIed6lG&AVaLgAX&F=_`vSYM6?XE!E$X?rId%&}fLU*qfrEl8; z&y#y&H0;awjkZ6fkM976PiWiVYyfgRNM+s}(QUaukKl)+8oH4HK05?yZ?A(**i9qF z_=22>(n#)_a6{oBXP|KF*b7mJ*J+nswc_;}%}QdUoy3)5`7#IuNH!{jljam|`2?YM zwcJxa8l{V~Q{E!^&*82p-R&32fso5>y8|R4rlQ7!1HW z1$aoj<-=B{Z_Bt!kHXb;U!Ou880EKHd!e)lwxY2tSpUGfbD=w0 zv^_7CbiNpKDzvH)&GL8*ol&liRoPS>X?!U=rzy`z8R>gQseG?y7-?Le5u4H65Qx?6 zgqwJmM$Qpof5`Kf@6`JkzunDL;JFBC4B-qc)Jt2M zQ!VvL;Q7@(^D;zw{PO1}6n8)_1Ahg`s1D5{-y$d}HiHPzqfuh~s)6Bm-}>L#+7e!G zvdRz9$3cMj-x}NhRIAKQ4ry@yAgT-I;s%dVzG@r!-2y)FHIQL#{KKF!HC(!nZ+xrs z<);pz&u{$atvp~WjeGqTbSD=gns0aQ=i8vDe_=2BTz;D6$Je~r*gu%y$Cv$aW6*th zKK}LJ*~CAY-q#EMN8j{(oeh+Z&=>T};rxSj`tG}6(mph$exCY&U{{}C=Rs~Z`BwWm zH0eGq^bc(Q^P6s7-Tip|7fZY>-q+#``P69kG?5|2C>Rb|GnX4dC&sJ z>z{QC-cR@ELJd;_pqX3TH}c!Q0@nEtw%dQp`XT24Up<6JkFTNLcZ8qgyN4V>Ttfn^ z`(XqA(GkCY-Lbwt2K6Vm_|Kz)ulZJoY4rH6e{Vz|xtM?N%kJ;l$9Md*$<>FvYCsS9 zD_8N4rudI<{m%ujo7hjfI|=h+cK>KBL)T_HfEfQM!f_Wp3)eQ9mr{{Y~}dr~hx}Yai*FKY83u zRUe2z_!`$z$?M~GAog##fabM)DW$G9qqN=J0{Nny?b||kS#=}zC}#>QAByhWXlY-F z@IbEXWh5P1s$a^@_>=bYtH+(|K~f0od5{tK$CNj%=GO_@wuEEQeT0@0O zv1ewuVK9sDEPku@5cEjnoSStGIm~0b77`ph@t4z?hli}06xhU{yVqlGpe}feCj{hB&dcj#oS2jojh4u z`efDb_3@!5cY!e9Vv&x8breTxQgY1VBE*v4%p!DAtNUegDp2OQmd+V(N4g}&`#@ty zfsZo!GoAbDM16U0JxzdNyC@8E3}|D4a_ev&`8;DbEw4>F?b z#@a5%6^0r!douF9Ka^KoFz>Sb@>NQU97wHC1R*}|JIJsKc#kufc*!lX*?IBiz} zz#BPb6I9|f?xy`FPKEy)J8-YUJ~!w+hxiWyliL#ibPDgw{$50KD~Eph@1GUiFMs<} z4d=_x{CMi0HMd7O`qM5xj^C*XB=N+kzwxc#MWtVU_22p4`>K6@>R|~#t~sD7?itr1euLxoU^loiYegZ{Y=&PD&$#<* zkN($RKjK>McK#&!zEd^b|H9);{y6*3!2X^D9(rU3T3o=ReptsReb?hm43X!+Z+mYj zy#LAnv6A?|0~d*XDD@5HuUjkj_`TBj+K!WbN}uR8=umkD;FpDvl8_4>ax{ifS1qNM zj50mP^SHn0eIIj+&g}EawW`vbC2(f$84=Yo)LAQmxPERZneRUnE?>MZ{uZwfr5oT{ zivYaz+2sOmxvC)13kk8Zy`9@n-e+%Mp$8{^Cse=h4-`M4Tl!JcfR2*EO+P4^?mF7v zK>Q0}@|SzZ?{~lZR26+X-j61#?>U$r_VQGD9E|8;GAH-+Ae_EA_{YybxIBQ58VIe2 zOXp#wcNY6o4rVs^_Qz=IlUoGv$8#Y57#QUqKZjb>?O%L9_S*^k2Z8se)eOr$eG5-9 zUw6nt^B4A{q;?ZEM{If9UNC>^xTek>!Cv@UETctAn#XgqY`K*}>?)A0TQ)&QQ^AE> zPULa3$9j1gtlW)(xN`9NN%{$Z{uS%^uz&%_`&gncIlr^P4Fw*cPN(leM{3o?TZ;^w z^xW9u^>Voib5w2PgC)lc(&!j(1B5=;`HZDA(>W_pC;{463w{|YA>CWDHSl^vMhMy zN?*#^!d}Jd0@qebk)Gq@36Yb`$ue!P5>)1s=y9?Do-Yo#0_qF29ldf=k0#k4B51fF zM#E%aGu|DzsjJC4To~B6Qr0ohowD*$*sUafe_r_`sXFEnb7Fxvr-`O6@OPK7#!#C& zJb8Ye`^}072%{$4a<^p}Wa}lX<`BP>7r(j8ud+8Eg9j8sbb^(C*Zhys0GjJBaT_nK zRjf!w=Zimxhe#iflS7RZ6@s1w&*~?Mn{CrEyhFKDNi_a)yCFS zK?&COE5Q3><$nh31Do1y=tK1Ip3x|!rzF#Su?d+SWpC{+NLyMEk3DT$!D4xKv-Qm1 zW^HLYME8ZMX{ocp80@`JtZW+=p4M^vnjrv&#dm?M@> z?*$zj%e86?YNQ~D$@3&m&#pBAs#)!HyT?z^-{lgDbyb{ewK2-e1r@VPgJ@wyRr`IL zC5F`B8MW?~dy0#J{THZDpnLz`koexp5(be9s6Z(^oh*%PBtpAqkmpM;-}7{cHqyRu zh^#nl^=!ld0hC;htB&Q7O;@uct1;a#! zn|olmx;xDudwM=T@-0f+`T$$-?yU?u%g|9@sAjpHiqI~aZC&l61n#)qP=Y#LF~%sL z>k_olj9EuJY)o`?m^U>$ZlhhaSoG^1?J`zYvlj{)f$HTpou!`y{Qr|PE#e{^=xuKH z_FiMywOy8^zf>BO)6 z#>O?G5r2aSYfHuhqQ^lgMI~Q`ARk`!>nrZ843>p+`NXZ|NYrdMU>c zVX$mWXg}VsLubAN!pC%J$rCcB_Fc_;{Yku-`~78NjX101^m$-+D30z_of?nl4pYAJ zy{`=Sjn;Fp+dhrGTdeZl+4Puetx>EM9SL-9Z-oWcVrVY(QHt|qa!B-6wUP2%1muy^ zgp3TNt8z4x@j_r`;^=Ud9I80*BI+C*VV9G~I3+j1t9$*{)ARd4<=0*O>#uhveg&HH zAPftzUwD_i0)OAZSqjP=&+IaG+v(U)aY~5{c9D6o0CBn=jhwO(81WgE{t&0V+TZUvS#KB1eICQa-t#+W ztF(5TF2Zc-I?)dST8i|wJ(qi?Td5KgmrG;Ols1|#&10WqYN*ZwBC_e2>oGKpNT)7< zP}wfML*0kGZBGn>|5OtB!pS~60Kb^ykM~C2#IPYiw2%JREw%KY@L%7J^G1s3EDXd* zBnFX;*@8GY;mj4B`59j>HES+KT*i=g<>!8+qpE(SY3pKoI3?_GlxHqgg}P0`&si<* zgX6RW>crD0XUA7Q^FRJGg8@TPe`xNw0sRI<*cao!-I0Sh%JcKLH1zL2`OZ7|wmcu- z`|YoL9xlkUFK^@e)~@_s$Y~S>fvUvP6z4{(l%wg6@`F0*#r7Co%1AMi>ax55XQsB) zi&G+=3_|ZjT%D;Wehr<%Gdc($D=}SJOQ&HPnvAdLozm=PtHV$rbbrgg)O?~|K{S5d zWDK$1PRN|WIhI>U+zbR8b$nZfyt190392!WqlcZ7k=D5@TSZl4)!9uZ>1z~1_rNq3 zXRjWs3s<<)dw(~S_gE7MF74r|AnkZeoVX*|nlWj(I2`+OnqT)q=gv&JbGBz62qAOJ z6@c%lo`SYJG1_ibqUlMEzA{Q9oOET>Y#R=hL7wkq#6k-E)jcM9|lyDoT< zf?lURBOR!!rOS@o+xFPc5{RAU2`y$ILwg2CvtB54H?EVBd%m`p69`lvlJE$P{;hfy zs4-h5cLu1z>3nf|hpf$m`LzO`uNcy}Y#XP+|AMBOHvF)-UsD>{%`Xk-0UYe@Q-A$+ z@hitfF6+Qk3V)>t{#>P+I`+KB4}mgWraMQob}>R@dsK8 zyDVSeh*P2VX}b1>FG!& zCeH}uE3W|JkVDoE-1P0b`OJ`guy}k=gZoIqHHYxl7X<0N;fjK{`Oj-HOnk`6fiBFT z826s1UzcpMIgAv+&?n(OKWmMY;M2>ZleZTRv__ISGIrF6Zl_ix##+F{+J&7!yTx`o z%W3T}L9f7-5X)*tom|lO*5`!w*+}=Zt$@bfhhXRZKcFY;L$q`Fj}N`h@m;6v?{0!; z`tcS|kKa3`(5D?v3{dnIF08?tO4KkBtGe?=i~{>MFm=Xgwji9-IDxVmrk{*O1)SUm zWJ}}LwqFu>wc-S4nQirRaN+q`c2UZgFcWa#V9GC|>OV~c|Kp_ZgCy&#{4PX5AEMm% z@aK_V{4($R|HJ$)IE_EJzI5fm=m5xvem|&F zzU8)%g;|%pD%ES`IO>DhVRx)g+v#|ZZ!3XQmfK5onprBJZjw4xzMMXJ=04KDP@Dk zbj-{hGbx!Jol1MkCHFLxzG^#j_!Z?_2HfVOn!~iJJ_MbG)eOJhT4q+P9p!=qQlYfB z*!br7pd$mY{NGiV?;*{5H>O@~syxAGjL`;G&+4R~>-_}!W%Vpw(MI3roENSlUu8{B zJ^Gt9t(E6$G*87ufnW#3SN;8Y-FoBGcs~^zda>7lYW+cW@>7=Y6a!YQsRpvScat9D z6R1$Y+sm8gsqB#9mYT4jm`v*WRh#O%BgU#x83AWP&n%Ntv(=iJ%Cvd}P@NEK$GEoq ztG^iwTYy|#C*}1j_L6+NqHb#kq_6{H*ZT}1_I!?d!Jf{%lHl-6y8;KZYF{D-$b@$G26H@V$$t#XUVx=xQsH|aQRy6Ug+I3bfc)HmiwqK^ff<2Em-!6y&n&y&Odb-mx#5QF>+ykj#KG^}nFJpH`T=w5?iZGw7xcBAEfraIkwRq1P z1nQzu!u6S>t+nr1qp|H#GZm8j+omAat99yoOoIo2P~uBdB%;lZ+a~f(PUJKS&yAe$ zjjQL|XmplkSwDj&^A@OlUfBu=TfpIa+R8J$y`N5y`$HN4p^y#}bg^~Jed3PC#EN4^8k;eKwPj%YcbHvM#>E>Q{| z4dz+5LX!>enfB+9sxS>fZ};wI-%F3c-MT%=EtgG^*hwT-4$J9eJU*NijOqx?K;y}3 zb(jEuMvZAjFCt>2%(-JSr@EtbIn5Kp0s5HL_7IwvpsqDU)^_(y*mG2O0-$ewJfx`x z`S-V$OOo-Z3mV!e54(eLNOntWbK+?Kax}3rGX?rUQtRtzMFyC4jsOF2X%5mwT$sv2 zLO1FT-EJ~jCOsfc+sDlm!GjX}eH09LDI5myXit9z+QNf=`whl_bHncqn+SLHRH`w% znZ>nW7qmX%&5jDnq~K$mwe%k2-7;PfAv{gN*G?9C-)zl`$r3YSx$-uNGf_|8D4XN9 zlOJc=#lJ|e4E5&IejRpw-rt8lR9D^`dPT2<6+bb}key1ok++w5&vp~*x=n>TUK3F# zZO$hVNUo-b>d=>qR2abD-$*}M>Fh=Gq|0Kt9zm?|=umY)nLGN6qe-4y)ZvZw3zS^-_0i6$u(SV=$0S7f(4 zs_eO3Poj$p#}jk*W2`{2GleX$a^ufYkf;&*qclEu{Ql~%k~!-8IU-9MIf+{}!Cubp zkl*4Tus-k=QGUlvJkWkXaC-uuGI-zzBI;jlqwyLv?bD76@Tp1rVPV*I&~RHv)X1X1eDc?y28X1G1Ti&`;nR z2CIH+*VFP?9Wz(SnUfu@Ggg(5t$YHenZpFBFV}dB+2#VR@oph3i41{!;%Z{z zDYhNq8^5tionqqMkt;ieuhv^{^qkaJHtpqk3M|?mN$Wf6;1g6fc)&M@J~^xEB1U$k zU9P3capwfVd>M7qc?l%%lc*0Ty>XT0KvsYZZuT^%HHKO+WjHN@&`okl?Y-Xr^Gx7 zU@{4fy8sO#d8Y-S3Ed>fpYFN2mxxWw;m^RNnQnBrQ270?-Kr*6$%l+Ju&3=d=|Z z$??u&pxwUh<%QXb)8zopur+zd{ z0IEp!O?EZEu_1wt!VHK1`z5yl?&RsQp1Fv@U*Ch}kFA6k_n$~|X!?of2ueK)v`{u_ zQ|qVFnT@yG+zx5>*iMnj7}IFVm-(h~_aH&nF^bU#UR|D-F6qkI)Q%!{#}&3yOR+`+%Iv%mN)fWsMx~X)Tx#&BL;LHKpwCr%ycC->|`qfCzGrfKc5irh=cmdyDmg- znT2jD=DJFkTO|@!{haU$wS}5qgI$52C(xD~gbNKcl|6h#*%}E1yYuS-GznYiXXO$8 zm~`ul(%15Imo&>PI=l=<;659?`gf<}lg9Zyht0F@`f_2c63>Rx(tLpWQ-WC7S(v(9XOik|Gn~LT;Gl2 ze&gP+qWe!iw9#%3>%BWRz8Vv0Y6#n!DY_WAl2)0GOcp}1-Gv)}g+g+_PHj%L2w?*m+^ zDsyZT1h<~^#H=mFYt5azEbO++=@>b0B$*(4C)(A}S)uJi62+3$Cdly?P{GV9^tPO@ z&H4mKUitSA`~B}-u0cG>gGUgwst?NrlFfJLddopQdAUAJYG6%zEYuLYy|+*^rsE-v zx6!niHK(K&;@m1X*yA>u=kg`sEMek|8=Y3mSsj^1mofQ4Nfv9J$6QTbWoJCKFoi6} zVMF?3iYy7QJH7Ou{}XogM+*f;m|q4&;k(XIa-LFSD;g!M{01 ze>B|zx($bKQY-Y3^i4M3|O7N|t9`q64^^fUkA|syCw7*lfxeDiT{t zNRrfkL9BHEuy(VPqV@B3OB_r@VsL$j($0~4Y&1?c zn0itRPj2>sY>m~qwu$vL3n}&28tGi3H5g2v{u)7OLz!>`O(DIVP zCsT4>T_?-4^Z!@Ba00P_Q(Pay0Ny?C?mtvWf`n(7E*fn|d-bs8&k-O+n)$Aodllj( zvMHi6eVDG7qaX|Rvl>f@LoQUldB(@JwhAxUae1ik8*f9CAu z62CYn3$&qyHR4rGD;`7Djva0^OS5)fQM1DoN&aT<|G~@-z$*t_LvUo@eSd>q@&e&R zDesm>*90XX=chHoEsLX!iYlh`Ts&$i?TRd{>Bt6Q5S!of)RpVQ^MYiKY&0*0^N2c2 zRJo~_2jNs|N%izkU&jW+#BRabAW!xOLOZf6?w819Gc~mhoA~}>B^Vn;b+>_eJ?(aj zbYtusbj_I{ueT_}@dA+{h70xZ@BVXMDTU43$RJI84fzF z?}e9%SGc;)gJ}ey#t*xyh@;G%}=OxPI^A+`LZ239E8+87@Y49-+L3%f3zU0D_rS3Trjr44s zgnBTE=j?L6WOqrGOCf0XZcF5{GI~Qy*&?htI|p{s)T6RqiCm^%k3sB2*a)qHo*N78 zoiO2Fo%L4-eJIG+H=qX?;pc3qw`a56bE>*V*a^iov)Nwv=p|Cqb+63E=bffs_I9m- z_EDEEubQ!6<$ZZnW|gIseC;qSycMS9DJo~_b!Tv_OR1}7awcBc-+1yYU;Mf~AHhlL z_BY_mPY55FYyfP5LoRmUJ$YmU9&0fC?91F^Ub&Dge|LXF`69G|>?vd5@n=;`taoGj zOuPNoZZ^B)au!tB1~umD(hb=SmTjf#FvTxxB&<;6pchDBuOehet=3a&=Jbnh>0Dpl zeSd6P%|KBHzahgAc>h3FdU!}kP`o8UR zpca4p9Mb!7&_EOF;Mu-=7ev6ycW2`smh>i#_Lh^IdV~X7Zt8WnBR5^6AfPqX)iyR3 z#H0lgEp3@59s^oISA-3GD@LSL<1zr!gdE|L;_M<8-DTrhEd>7C^E1zS-x>JbgS+g54krY} z8?5Ye2tVx5_hb8hhlVoh!{&e5q1$y?e>=c44UouxaKE!t(Zqb$>k|r8Su#xvu*EFX z6gN6LkX3H`^wN~Unp%1~l{BdlUn!U4y@|*0t4fD`J4LHYe&$WH0%mbj?G%qlULAj! z!uL3*AM;$GQWs^Ig;#PATpZ2XqOZL1a{~7@hl0s1&-&hz7@gVvY%vSg zmOs%hm^r=ByJNW}_Ab$S$@n_AR#$i+s4Mak1LF=isc3^p@GjsTs4=9ZW3pG)x*nuy z9@lCuUl}!=JV*PLvFA^#c*H4pn}na=*ZTV({{%oOh9Do3F?R&Ydv82xIrAn3J!45u zN6=6k=cNriGzH3~M_$^^Y<5Ar0OU2hkhUqw*r=Da;aI%Av6mNG3qT@jk0W8R-6-?R z(h(b7bng;H!??c)7$2!U(7J`_rTK&B$Y?}S8E5O_l}fL({t_?@YlzZh-P!DTf`m(l zT+gM6UP>90ge#LSuiDve_0BkEkf6^BkTGnz$WNGMxo|DsoL@cS)pO*PqX!Ds>Q-19 zyzpRJkNLbWByKp0dxFMw9y$YRU|s@Xl%q1$1)t*A(-vUpnaz#8Mb7v*;+vy9C3)*W z0{6!GSd2MY2wK6%R=c&&1?|AB-{pt4(^+$N}kR<+t>h%>F zPc9=PM{wXY--`!!QN(j!%6m=Z!@YwnVsg}@jV>kY?8@(;9}skOBl}3Z;)&W#IiCzz zQwDt-Ype!7E=!1|eozz!5QoSw&b}vp5Zjv>eJ&9HmUuoFG*cY5+Q+qvUkxOKe!>gEZ8Wal0 zubx$}V#p6m`*KP*gv?uayyVKqMzcY*1wndtdRmO+i|^*^E`_dNL`Qal%#MfZ$XZ;V zIs#b03x;pV?Wjpd8>hsKWw;%uNliOpuV<2l(ywwfP`i*^p6 z(6U1It76GysT6waqDwBL^((V`IU#qy2aN)ecRn$mC169 zu8bLi9oND6oUblK@M@mJ@w#ouAZyrsyE8T4VwU%w%=~7uu(e`iQKKA+;}mp1MZ29E z$(p@$5-$!GbTX}qbaa?u=SC3Nh|ea=C?-nV0;Kw!Pbzb@W3CHK%A0B8*IIgbnSg)g z4~Aj|XoB8_Ab!}9XA=F!Ui9r)=chlgK37@hHRZKOUZ$}TrCdixRXUK`#X6~r4mj8? zZh-)zY@nW8Eu@8T>G!KZ(m27~Z$*hnx->poIy=MJvrH?{$BI+htK;-4FK|c8hSwoh z_@o*cW(0T+kRJhvOarm+-864imZ53xV~^q2x4GXQ7;jmm+s#(zY*ZX({8dRDO`okP zcw=Auh8ivMlfKx`^eeDTkmry%3j2KxW*P0Q4XbMMev_}_)*9u(fvfm_v)x_1I08o1 zXO#3s4Drf4J_xsd&j|Lns}r8X)6YvZTpy2MerWcEatv@uLqm6)oo*}mAl&+~Aah^t z;~ufo$Ap{ijC8TIbN|yNXvpcXnRA#dNMIKyZ*df>vTI~!s0aRjzzHW ziS?-N2^w%7Y~aq>3te+#Zfv@O2AYKY%NYNqBZIL&iG#sHhGPB?kAV9OpGdLcmGoYo zN$gaeJtc;Sf^HDci&C`So>wJ4@sCpuHz>tF5f*zW;wgE(oL*>dDBXQTs(+urtHQHWqea4E= z_qOlH^CCqr!0LRMFL<6+$NZ@_Ex-*9TnIToj{GC+TzA^WwCpJO$yOxfWdk$3FdwN@ zXtD=Kq(&u)cn)Q&o3qjmJzQ5th@tz`mL7UI!H#~7b09zV{J>t$mUEDc)A z+YuD^)^%WyvE5v)TJGRDCzRnQlM8y?Z`PL-lV7=@AD&hNF95J--T2kBV&FJ<*ATp- zWe=JXRDc-379>APa^~2g_R)gw@phx|(2+FF@sZlQYp0Jzk;1OAuJw7ZuC z6qz^Z2!_?r1PUkn&GfNHw)w=InToUXPuvJw&e60@6)_%pZgh3WS2ZMeYVBqjs~1Pz z-Xur)nl=PBb$ETUjy1qYo!jlK=_g@UVMnj-Y3k`Je>s{kOn9CTP`}$4-#eI$)XQqo z$tBH{+ckTHuDRurn==W+2`G^XqA>DxR$%rfVBXGA!JY_ja*C)X)}*t$U(G4aGm3J; ztqgWg`YD}aKKTqVe)f+r#PABT@OL58_dKLm%t+q3#B*l@g&8SCIEG3O+VQZNt^Ez( z>U*9q=gEGw2`kOM(6fx!0EDb7mZtB-2`#Us-G14ZZh906EO#YbUW(5VesSSEAFb}^ zaNmzi-GttB(+zoayor7B!*0qI=lx^_FT2rhd60AotUA-OZm-t-C9yzbJleTvSvBh( z0m2W_V1#J5p}14V;%gq4?2i1z4}LM+Pd5U?g>2S?Me+{}m9c;g#S|wY4Xq&fS$B1s z$GF}&_YC2Enhe%Xf=}7AlJ>99+oH4^{9xW1zTB{kDwK;pq z{qvJEfq@UAns74TJXz5~;8!y}GxfQ8#@qZzG3gvR=4x7EMJUe^L327Nbm1%Yg7@?U zc=|o60(P%@!a3p|VBPgqjDU$?goVpMa-A7M&tG}JA8g`w3*MIZz0MA4F7~RmSS(Zx z>2Rv2F$P@(Xt(vKf`NV-JBQvb#|a$ZcbSYPi(09ZTC7SkRfKERK|=pD4v##MGX>=W z&VIR>Q}u1MPyaMKs4{_o>4GO9|J< ze>LR^@QKjA+D=0^@%*wWE37;>*n3T|t?kTtTqmLQNzun_n;vHjx*|qE&ajya0>xVb z2i>qi<^@i|NXrHAldso$m?d0u3~IM$$4BG}z%ZY8S2Cssiy8Hq$Nst9sQMlL12hrch-@WrD zeEFxL36TcruGB|7tD?>31%~WU+s&gP``j>-9i_MAGeF{(R$r z@+ri``A5zAJ#RjaI^dl%q*Q1L=xCY{R&LDCRHyATlmn7mzpk4cSs&^hIv>q>ds1iA zf?|#~DVtNvGPGvOU%hZQE!kGA5TTQtu&)^hiHsBQ<0nhfb+2A|v1JcsgqTH3R-tHhw^Ebb z$arNRc$F|;%@GvPp#Xr4#1UtVHI%OCy#cBBqb%l4`>CY(#YmrS`l}#&82fN7zPHLP zOQ8wNd7~x@2+Fh4kf)6)&)2+q6f21_3vH_ImeqkhEnN^A*N$Z%>h*G!C@P^aOU^rz zJP1R!fj=$e;||$})@O44i?Kfw88N6I0#;}k>U(tn%AyfrqqfD#-L9Dla77-@DM9z3 za(yxSj&a=jlz6Do)cE$S;IlL?6?v_kfZ=~aRLMb_VJCaev0_AGk~t2?)=qL?1speM z@y&db{Sdh?3AP37*t-w@)4aPF2QC>yZ`WppHy3q7 z7!0l%ls9*1q6^!Z#o7`Us*E;^7pu9ZS2FK$%e^TUQh{v4rAAE8ROOO(D=M7HHp%+c z{9ffxp40no$5M#TZx;AoJD?40i%wS+uxU6nRLss@xU7&gffTV8Ok33Qw3CT6>d4ik zt2%PrKr|g?y%R^oVd-MI*;%u?^S97)sqF)(5kg06{Axb0EO2O}A#g}(4RU_LIq zYnFNglgR*1fOf(Moa~nEfT@COc+HH8*!~2cW^`a6k2Y>b-G!f zC#_Y`OI=(W&6Jqv9v={=NnWC=tL|4Bxg!dRUQEnXS{`PIE=bs`sOk3Vcp@@>%)|_t z8mRKWn=r@GjSZZ)DJw^teA;PF7GFrGCydp~s;~n=lWDGo5l$aKy}O zqLEgL)#RqaLpnnt>v&Qrn+!(Rja2928b?86*jx!*03rm0ZdaXK1H7zk_iLyQvSBgf z@R_}x$HHrQ_nXGRAgTEy@%AC?ATWbgy$=eqM~7bBbZIyUqqk26@lUq_($h6i&JBee z*w!!Osl1M_(60uvgZ_}P{m{((gQU>a*gLF`MQu(x4JmqY%UyMcH}ZAfQ<0P}2xCUu z*ReWk5h)6d!&+g=tnC)FQ_8xo=ppSmqYpy7H5bjr#+5!Y#b?6di%a$yAlz;*=w5{I z?b}^Db~L_0CH||p|MWix)YJIANIpt0OT^k|xV}ggkF(UmSQ?~mk{VVf6Nyy2pj`v2 zP3&5LJ3G_C$VB*Tv|$2Do=a_)7`Bo4)lmuO69C~=>yS10zp>R%+{5o8{f~)26Fl%F zJ$LX|nAG!g_ZfeYm_`oXRfp>xP!}`gItKzp3=0hrK^#U zoznPxI$3ZakHTrz;^LT4=WD?ikPw*&-r4wV)bsz2CqHlo{WT_Da}YdN6;>aTx*zA~ z*VXqS{dLQwegjh+4jY*0k6ZA+{`ziXezTBi?hg6>QxxxSS3CYsR+N~>YkWbTaX&W} zc-u3SIU3oAM!j^0IL@%JJ1*AT>|_?bS0yX@A_|aCGbWja2wtOC!ANdB%XJK;fb^i5 zQt~EzStv4MS(*V8!&~)!NkG29^bQl>VRpO%LXM|HaSW^F7JKV@Ez#_e+k4 z!Rg(<_|>PrdH|KH$JY3Ta(Du7~>gMNv`X_+W9j=47BB|wuG$9Axz9C9^XO5_Z>8Pgd)UCw|kj_PeGnC7a!o1&Z3WgV>$0Xv>?lnU_UWkj+~bDp|a9hw1! zTyia02%lc}YwtGtgR6Lf(Uf6k5#w+ws|Wo)jKdXlh9P6W2(AFmf+Xf@lugN#c3A0I zGUnMyqu|gf%{FNgsT*Uyib9}pJdYXORtvX-4)xzg6hAV{0h1hlzZAYQ=Q~N>uxk*& zKRo<1BlY7I`l+n)@ih3|W>!!t@4k86kT3u?!#6jP<1Q#h8(GDc+=|NF5vfR6J?}>r zbY(PRh2l7Ln~05oURD4#2UW_FZp3bRHKD~w6fSNxVaq0F@OWnHmz)-*r00ISNZ_GA zpFlsp3GTxOc3fa28cm3|S-nUm9(W*9B$4*27{$_XY?oKb5p&2j3}KMbIHc>O>cfY3<@z#4(IrA% z#jQ5o+mp@329gaT7SAr~ZGkpu<&L)NO80_nk5!ZH$&CYGuy3;fwN!Uq?NT63qU%=| z(~ZRF=~I8Q!W$>%z+jTb`@@|8|Ggw3(c5abP&jwPijL2(fdJiDiC9Y*Y<*e-9o|lN z2|pZ%qg_?iN_rAB0c5$>M=55VRe_vvd`pMslCVtGZIjtnXnFj3kbZZAdhlNJ;tEjT zk1YPrpZq@Td4BrCAis=zes;Wfno!`Lc{eG2%2PncjBp9`QQ-CiSL!a?{y&&b;qG`h(Pg7JQ=XQRB8_TQ+xD>>`?I4iYgH>T?NV4M7VGw5(V|m)I-kqD zBlM(O(YyU_9IZ`iv}W+kx)#$aVQnoYl>q@lujGM5{56B$s+s31Nu8wiC+VgBn?}5Bo1-OqhG!r(hk+&?Cv+*^bX{za)2m92@tvm zr$FQT-f4rXDLLEkpl7B{&HRw>Ek=X3z;uRU6Vk-P6qksQIRiIp*ai^bny73XWYN@+5dvq(yD_7D#hH7r z6y6;8jbZKH3R<+;kac}G{Uu#?9a3OA2lUWzDUXhh-{6S$+RRfhGsCS|XFrZGkl^K@enK?nDU;m6Bp<|vMc=m7E?jbDx z$*+|)1pRN1b$veO>zR02xN!BDPBK-HQw9{BdfVu=ap9h(p~fJ6u)R;sO?Xi?)14(j!1PO z@3#ctCibI(muJiANnyt=Wyi&2VnC{Ka=gxXC(qnH;ANDVtuGHVWwe@^qP#vU=FmH+ z6J(FKO{t?maG}_71yJ0Z?aVn3z6-rQP1)af$h7)twN#)OB-Kn0)=me)P({tgLKmN*E@pKmE*SMB5#0m z?T#NZ6H;fLl1Od-fJv8Rf^0^N6Q8!>grObU#ZL=ws?ZuCC*BMy#HbW^tSh!2_jY41 z*NV=z_R|7==_`e86EQ>+Fa&pwPHj5d?q_h%dnpP<~YU(X9 z-J2-8^9lVTR^K*M!+q%PP`BQxUi~~rW>-=Gn)R*w4k3PermiZpjC3N|aF$=YaqFTk zw{r1;NEqyXSP91%Qs5_vJ_!S0U~mNw@G_wbEJNnmcB}zAK|G&D?vffbiVDC3k8}D$ zCV$ku{z)kJu6JkhJnyoT=kcfFb?PGy^Wz6N`(gP}A=CLIqq}}B!j7e>lL&vf)~V6o z5&EqqYOphVN!C|vJ2h+m%zIJbM~+u!6Z!&3`s&tKrd&96>=2ns^#wm0$%o5Q#5T>?=WN!4lMvaOzuAAujPD=K$uTo92@e8Vm?% zw7a2Z0IK(09D>M;`$GRtkalQ6Uqa)*h?ASa1uy21K^S}E|dKIZ+E`}Q5Wl@}!IMQ;D3yS$BSLLtfRqV>gV zc4}Get=9Hynbljz>J(}fzYW2#2ziB#=0zthlls#<~;=6uiou#xX)5Hh`aS5skaos zs|D{*2pqhgw5MO|mq!S?WQRAC{jC)l;TGA1^U)F!*E#MbnOF)Y4NljdB-*GxPXG)z zn~PIyBJZJA+C^u}R#zj2SQnyzPF=&8=?t?wO$*TgR4;1a$>5T8(RCOidUqqxTNvoA z5mh75+J(oHqZJqWx^1JOY+a85)9u251(um}XGJ{B=a3-u0xzEi)}3rU%;}s`lPfH2`S`1!{nX+ZxSLz}(Xu z+sA7!ibSgfB#UV>4k=$9IOAZembvWk`zG=YptxL<#$h$E@Dg8>0snDFpGc=S z6YloU8z_XPrsMQZ&7^WXF>(78}8GWe$c z@vY-xUQebGrwe;POD6`yMl%Egt3+k6)gZzbcXkmsEcMjYL)h1@t?{md{m2AB&mPMGH1>V7A&T@7sDRDx zBNZP%^}9s*wUV~K7+>XV|Jj$4=f2CS4!ip^_KS<#T`hpGeQP5ToQuz^8XvXnP86K! zZlx)W-7Htk613BPS2~^-sUkOVP;ChbjT)L-%;I>l$s^v3t4tV9WPj^Sr@(5oE46a6 z*xEPQ3`72qL?1(RtTgHjnJ!8Ni~sMaz()lcJ8f1+v6B`f0(iA8=V}*kjocFRLL>va zzPJgJV6ui&`*CcH#C@toln#0|NSCMSI>XrGB1vV}+$Q{GeiY;#&Q%N&f+c^peH(LP zeHua7ZnoIA5A*r2jv#0b655(R@99S+^hd=rB$xk=R{44c{u`yrH{t)W>2t7%M9Wy6}$pVi=MZv4IweVIiFYfNEbE z!6{?4rCQKVyw>n&CHki;H23&B5Al|cJ0SQg<3yZi#I^^u&pE>dk}N$m`JdMOF8w<5 z3@+~f=~ed6yJ7fopY8$euM7DMbiWn~5K^~%AiL|o_$K1--+TYn+m-5B>!VEfJ96GA z1z7>r7tl`y)F>S)9 zhxP!)?|t|dK7B|t_)vEtu{+fIcwZ7P6&b$t| zEw$eOSPaT1y9+r0hoQFIX5x%yXt~a|Bb7=wUJ=Fq!`J-NJ$dsceBGL#zt{)Q8}rL^ zPg&BJ&h&w#KT9}h&FX|0*hn3I6KL9}71ZTyd-jOu`=0|^y~Xv*XYU&LV}_rs@vnpI ze(9}Nkl!Z#S60#IgMJtEpI2n`&+o-+|M~9}c&6JOYlEeOeGC9jbqa2Y_kvb=E`-rDLT;Z*IJqMdJzgJp^pN{r`q6kLOrnd51luCx(0Z#V0plb{(oToS}jBD+;gTLn?x##WqgV@ zm^=5$v)n>SJH(b)ec^>v-G+sj5BHUK+5@4LnNB{D--#RO_xIoHM%T_djLy_H7+yetRJ0Wb z;WkrHA-cMweIP}fkasx%NUZ~LHkOw)>z&VF9<*m5C#d(P;GVAnN<8tZv~0}`$v~;xEo>J>yU!&jVK6|e%4s9UC#j4J1P@C{Ka{@1#7Zd7~yMx|j?Yh>+Lv*p8F454I$J6=HAO{-3 zZ!J_J0GTNz50VC1YGvy^MCGg4-yJ~aw{Y`c>acRRGrzALgU+w;_AK?KYl<3NZ-r^;%0dN#2=cUI)Iz}XAUK95F~>L z926sbE}Vk^1o&(aJ%bR*O-(&7&mWu~2OOCUlQ5;ea`IycAB9idp?EILpY1VF?$5MI-{A2qa{1-#_W|E-)Lq2)|Hn(P`se+tv@Reito(4{{3GY!XfkwWkp^6 zH}HVn7aO9qy_S?&Z!pf8;YkwpFV46l@se?e!(GX*jW)4?^%oY2R161w& z+&Ij2kSgxAVaJ;xbBxoSgN^N}OE_;wAO8;w8>V(^E9#?Rw?BTb!M=o<`f&BvyZF*b z_@$b-KgHOd6qKTy3VhIu4eM!lwgKN;4J0DGH zH&^7X>4{KIfG%=sbrmlG>JI8sn4N}vDo+I2wkjMjXwwBI9yVLP=IsIl_~OOhVX_pk z}n>6<;S6ZHT(`pq0_SWvhUwrEc0jABn0(9en$WR0_?4s zsGcmyt15I@q@kQR2^)jq(fYjeDB!9b?~DD887N2#_&Fv?%vMl*9LpO!JFM80In+B9 z4P1mf(8hAhq$L6=pz~b%Zln6H*+*UV?*`X3>*sMhsCOZI!=XM$pxHs$eD*Rwdkdz1 zhFr+49ubxRdovd@KRXe<#;>dM5;z@;+NC0VB&Mg!0zK^nBRgHmP+m7`0Kf-}gl3kj zJVxz?bhfAB6i8@@HK*}@s-9!KZP7rZo#6O{4t=$mU6lA|-+zP}<*S~MoXI;nmCrjaVU(o%g zd|u&BbG>)Srh9kUQ2g53wRj|ip22g7zgKv&jNOH$k*JA9%93uG1Eto|2*L@Dhm<>Z zI>fQMSX`G1W?H+8w~jQe3ACgM+?DybE-$3Pw?)UV*ZB7}xnH$E^-A5>1U&4nQ1{)% za71N5j{)^hHjBhOJLQ%*VHDscDc3A%$t08w+s-yBhL4Fwu`?4TK&Z0=IA2`360_-S zK8MaAVkQ;i9NPg)qU>*;D&)mtfLV7_h<_4|4@{YxfVmx?7XqdOiUc7E>++Up@kS;& zVHnR9j_qhP0)7xzXf14z%G}UhxyFIw3Js+o*3p)(LmnA#aatlA8B7+l{iPh3@)XKR zQ~Wwhm@y%b8hkaG^;x6a$^)KY4u%MDhrNe-GAEK16G@T4o3 z5u9<6EsXpay8P5HfH}{_k+#yjXPJX@@Mh^n2`5Y?0p8W29SI$Y-ZsM4RIy+2y_LA1hiY`M_sp(=R} z%T=CSZH5DGZeT@xHJv47-94J+jzF9=bvz;T)8DOC*A3NM@4Z>wt<~4L!l8s0 z-#U~ntfP!-4trEl6OH0@G+&F9E-zIJkQQkuRJE7MWVWkLCaJV`GbDYT^pe?Nvr;pn zC7ha^=xZI3ASEuP@`xTIt3fY{z6|N3nph_yntFyPVjO8jw^1)Vkq=8>ulDm8J;j%%Tq0g<}e74^t6qF%CEyt}L z>%G$e(5JJjdYRSRDc;hEDsG7R{;wX!2WVnU?z^yG{bss;7SK_td;dH zCduIu02(G&Tjoz=gGm%J53$jAis>)S)LRWCL@|2j_SPD67Y)5W|L6?tRQ3)3y?-J8 zCtPz#twV>*KW$%!Ucc^2yyv0W*UM!tidg2a$U}L#R<`dMxUi6+(ri)?Oeok>lSstP z(K(nQNrl{WT3|T3^JWNQS&P7=8Gzin+8Q^akWg`DPf@>HgwC1&#sd7$2srNnIUt6f>qUXJjtpYoi|w5l@~OpclIPyLI|q zD&926^Ys@x0G6az0v`$lp1o!uTyJXSZd&%QKBFDbP5trsU8g|zt>N4?OY_q*0zmkM z3f#U#>TaKd-SnY?etG=XN&fqXJ|t*AAdpM%8dOm`r)f-eFL`ps&NU@E{BSLtnU-x1 zBVl`zd~%r$#)niz&*BxwyU3$~v&6u^t`?)}HJu=%2Y( zt#Yl|>-nUu>N9I6yJgN*K;kswUG(57z>9syQTQ#!{YrLTZ$1BD9X$j^5W&p=(eHvK z2b@fPSNQ-E=}nU$f<}X_Ljv!5htf@BO?H~;1+o`qp$wrxoMP6>qzOEeTrt9aF1gvJ zX??)HUi)PURdw~+@Wz06Q#rG6BbY0u6_NZgr{AP`3=#LAfZ5O$bAvPkdG^-I^9xt7 zWkV^b^%}FvO>-0rrY$jkIM^Mih8eOW6jAccbxj}1ePWI_ER~|$YlTB6Z^4MyMd^nD z*s7a=PvY``&K30Vot5_x>9^zC5Bug!z^K7gdu!CveqgrDaRx(ohgW8H=Gyo`T?Jt# z!Cu601(q8l$$P{RGrOKr_zyfA~eOhNbC~`6btZf zWds`x!cC~?U-9h!ewK!|{A~H(=msK%M zoB?fjLA&S`!r^(l!*z2B&=Zv?%iF=}v@l{ycPg!r)nR}%g*YBC#7S*fi?t(TIa>|! z4aQ^5Z1T7^zYX%1HipU~5J+#0?-Z4*LQC?9vivHn0Rm;3Gg34@F6JrFvn1D1DxVEe zj4s1-P>d!Qn%LluTPxM#(CE|6S`#j%q;q3YDaML7l~xwF{APUJGWP|Qf1}}<4gDoq z);t7+IAJr5C`}sL+^Pi#R`ZzSW2V?u!4xG00?QF^Bgvb^x}|80_qTXnj%Y2~+M9zL zPB@J`FswaV`TFX+>)Y94HyPg_i`#hnaO7KS8lTb4G0?Jn;x(DPsOi`hR>K@L&<`&z#M5?! zw!cr7@2AmsY0~#jY;wGe*U|_#XMn=0k2#z&2m0P?1_E-%?VU=Gd136%!s`@T!o197 z@NrshrUNNexh)HDy-Q-CNq{3;POdW$HCMJip1xb{C#dJwrp4Qxgyz<+iyRcyTWfzR z7Qn5rSGT%uv=?zzB|vXJH|DMx_%pQjal9U?r_d2K<5(<&kwPM=QO7L83*dS-H;P`|+$qlgg#171Hzss%L89Td@G$cK zdVb>bi2?!A;orSAqYx-4i(_#lt_uKNM7dQqjC`{qZE?#?-6(RB!wFbg99I{cvnJX( z?n5sjO%yX=zcp<-b(-~%7#qN}X3UH=Dy%)aeeB0u1|}f!_krvigHjhe>|BdpOakgw zM=j8s&7Dkrrgb0Xxv*qlQp2^+ngGmEFZ35I)7Q2~QHmY3GLOX$jHD8L{+%5M@6W4g*db&InW6;@9Lr z_>=;aXza5+<&}~BdYZor;7-7SSq1CUBl5gy%etG{IhY)BTSYd@7-~aTgnL*4<}rEP z&rj+W+f1QX3qiEmP@3}vw_N%3sGbJmxsgWeW*d0IbYy19g37FDyLAElpqV|y@c;NG zKg^)d!l_H1e-O;x7VNE^Cs!@U7p#jT@(>3s^UG|hAzZ2j(mt2s5D#p7#tl_drqH<= z(3B16$u1pirmKCq$T?luspYk}IKcsTD&CP<>cR{kEA_2#{XK>CK~nuoGRD1h#Q{JL z0!}i7&_45INe)Iwc z!RO179xALp-)PEg;I{JiT#Q4z*^V26OZP!c0V>QXRj=j4IeW1HPx<`K0(6m;NA&9V z1-NO`E{G5Q#(P2h-OfM8Is)WFe}oa%9)nhezQDY_J+-tIX$MdOK}~kB0sZM#FQNc z6k1e`&hK}mea@L`*F2^4bF^;M0YU?AT5p2>ho= z-mI5E(60`S5p`JannZW20m)rAcAilDI4UMMPMZ}v zr_A-m@ucw~UJFGPOk_d0Ago8D1(7!Av49?<5z=BCrYZo~4n-14l%6S_FXQV5{7M5{ z{0JbAyTXiHknGci?mhzjaZn}LHO@SR+l^RIlGu=)+@GOL%SGewO7t7c22!N#i1xR}|twCHaODF}5=+(fhGcKT2ruWn6G z%=-j%x%u6KX-*d=T1k0yZ8DrCZ>*f%x_%wgyEb|Xa9j~wLcFy^5Ft$hRaQ?2J5L!(%Yo7gE7! zL0lZmfYExRk33UkJx%jtkE3yT*^sX@+Y9&9pA4ZK)OvaWT#~8SNCREid>r-t596$P$U8OIE+YFnB*KoXCBPYMwLhwd*Oi!t2Bgq$YulC5gB5o(TY7oe z4LkjLtF`IFr|+HG;|a3r^hlXevfH?W`Eb6KqAL%qm@!acD%;g~)kx>%@-oyk%_`4? z$*rgy$m~XN5m{qyg5KGLIYH50TD_=Pd$gn3m?VF zK~QAp@Nh7Hkz0@A+s>zdch`q0P1S_mjyGD8Rg=+N=A*&@Pf;#LfAA2=`u}$}wtv?CJUrDV$)evS@P(pf$c-v9U z^g^H6sB|*-mz3WL$EBRdnm%-8cWvl^)w$jdr<@sY{ljwPGo@8;O^qP3P+Bx=YTL$z znVgvwaB?hcQO}lUBg~%VkzX`PFDh>1>w1IUlMZJ9`pYT{tk1caZ@bBAS<8c>sF;go8zKhWT26eG_P zhbK`&izOi*%?f!*BI7Mzp;79@)Ck}zOM0sh6=UX=X*w0+lQFZd6F21o`zHB2vH7(7 zA7=go`sT~nyN7?hX}6=$;k3So6tVS>yTSlM8>`BYgEcR%vuMt?E-(+024F9sQxoB@ zigGk2V?0J?!AdY~Dl~M}4R?-Ro|q%KVBTtI9L@U9;H z3Lu|Av-Tk+xZ<~+216j~t3=`Dj~8zMfDm)MMb_@l_>C?0r<31k1)c?{R(=b@LDAsH zH#{0Jpws2!r7wVGpNA0iO)Op7^ZxrmpJRFUrS*t~p$mXk{pcYj@$sFX(dcgG;QgHr zlm)shz9ehj>5X`V8v7olo0^8=+FKKP&zjw9>i5pX#kmCQoQUw9-w@DWiUL;^o$SoP zM4&X?uW)Ue&*p21B#0?F9w(bQ3~_kyHET{DR;4*-aeBy=a;@^~Y!xO@3)$QlF1xLJ zaSgsW?hq;cGVcDUzjc=3SWdmoiQkIIRK{g#3DyE@&Ie&(_FTh?cFAxqKuKGs}NS$`|8)NTOE?w5w10X#+A*Z#>;k z>2KGy4-5BDJe?8vZh4x<7V=EEYN~t9RDJ-Ab|Dy6My1No=YF~P0A#)>PMh%s^QxI9 z9`-xK8-THOm0EU&?DY0)QU^%h$)tKkn+C0;WUCxPjtz z@~gir|MB6^f1drw@H)WM{?@*Af&ji9Krkoaft@#e8`%pxDA!QuB(;eP<&k{3wbxiv zqV|4CO|L@?1)gnAk%t_T+822~PAFgsS>s80QGn7-5iMS|oZc3I$)FvsRbLO(kLbNH zKUXh}VP!D_Nmuf5U^`5t#m$-^0@${tZi~ShR}V8P7%`9?WjDs6i2(~49U#J92aGODTxWnY@m8DD^v;9gww;!Hp8;!Vu{u7!TB+Ur*LF1{d5a(lb0o&RteK4c!e z1wEaR@W1uw+{F34*6N1vdh_kNh%uPOA2RF-oC4S5-RkGfWXGAOn9lfQ2CY~$Z*POM zM7L{+-wJzjx;jOxOPU#r*%pzra*a-FYqk|HO}$=6&Y(eQ2b(7QMaU#u^kO1Vg1XWk zg5b9+oImOjBfIzH-rU;KoE+>kPyn(zlvJ+x7^9bAeo_`ngawBS30@D!WhP?cy1Wq+ zu%jjn=vN=}s^Kv8B^V85z_VQ60iId0H{?#a9+uphobAq2~)Bz{~iM8t! zXXjJ`y9*6;T<2(=5KxhG++deIYqCqaUI3Q5rfxDJ^~D*JdQrWZTY`&fr&U$RP))4~ z)p5hc&nxlK*l0k;&gv85@2wq5t`iMm_o@l#6q00)_3$klg>zRYOi|KkAIVn+F z+R728MomWt^WyF1^DQ!+FBV6Wmy2X$n*>ke!f;RQzmx2LdEvo+>F3hbTGpSFmS5iJ zujhMHtl~&nOzI{B@_Bt!El)!&o++0Z1zGj|^)ebu(4VkJ3ta-B7Idxo-tM%sOtYRz z0BRgx%684OoM(+xbueiombnV6^1B4qV~V{Le&UP5n87)C>l*B1JE;PE9ww~m0~M4I zES=h!``j@#ZKIZUPJyJ&S|K=^M{Q?P@p`*Bq2jJPJ^*cTYih`zS+f4>zvrZZM?N~u5g$<`1}!3GP=eh#hC4H;3VoBTeTb4w zHfn^?aMgKbdQcX|QXa~wOOE)HF&veAI=q~*t53Pr*11&iA@PJzLC3Anw`@A@#EvYXH_#y z-q=CMfD{FQ@zrjN4d%)H@{CI4|Hrpnlmcb_j!9 zM$Skc7qxd>rSnnKjI&E%@D3AJ1ce@y88UY;ZcHtt`D_uMwQ#0BxihQ0cL`8?Xl)0r$Rd5+$% zNFSma3)O9Q3xOM~N=sCv!P(F*`3ik6C24zFM_Z%FeZp3TWpNzRORQ3I?7CkDNSRFsf-|!x{P$tb_^Xka^=2{=x3;aV+|~skNc-eC`DXGK7q7fAG>QImC2dU_(fKf5#r32 z=CEDIz~5%kn6Op8Q+eP&eTuStjg5x@LYnFq2v2k|wSH^glXBvSg1|Qzo;eB_HN*}Q z4Do@POo{awHQJSts0(p}GPr#-`0#vEjiKaScazOEvPbdspewsdR82Bbm&StKvsVg= z5W|O*d$F|-#ocYqD;Z!~KzjY?`}?J9*v;;~Mt9fl5-@vCzw7_K$-)~A%`b2L4SO^= zuf2`AU1wJfpH6uEEFX8*(GcL~Y}$aR$>zs9slz?9~K%65GH*Q zUpF@5(d7=&yqiaRJAeOaJmb+63&8)qB71N5l zd}Mpw@(mr%#~O9zzul2~>aI^i_;~LbT88ff9}T3#J%aLF^N$#DzviR&1b#7Hs!(um zwArdTYHN+AZ8K$~vNjba+OaeFL@GxuSLB*MsqOMYNGM4zbFzq3e_4#@rw9siLd^jz z`eb{lG2+=4y(WH650(O^1}pK_aWW)#Awpx$ zy5ttC3$Cp9iqYV%FG&Y7g~Z0iE>4^zM6+U(VIebIo;PY^Oe3zMsCu!A$#w|E-kfOH zBr^tXLwYcCpIN{E=s3XyJ|Cs8)9ZKbJqL+DrvVhEsERq?*Q<4D8eF>tFz~Ho(F^so z-D%r~L{=1$dBP>c#ArL1ohI}#T7{v9)9m0ULNA6SHU%Tgo%x`klo7ZOHGo zgsIny4jga?|GaU$>80IKN6#RRa9(h7F>u!ft*}}Gc6B8YW{Wxl$-k0T2+bMo@N`Z_ zK5@{KW0(SR%K?yRngzDNskfBOZ$qZuEO+#=sq3kADTzi%%qiSFxd{|8X1g<_OaS(3 zr?iFvX#_l*iA!2Ia7>o3MPj=fF=f7p)-bP3=c#yHFDJ=T^cjh3F5g91?e|x7zX`#tChbj4hzWFvc%xYjfUaKrnP#?}B6M zt)!i%$ER47CnWEAt)Uh^M=z2Ir55JnHorI+Ps{wP9_S}=;*u zwcoogjV=ok2#IGS77^0z(pS(ungF;eiOr9Nxv%ENW-HX@$aj#zK2@iUCZGV#?h}_( zbdHB|s~(>|_KV^FrH%Eg8PS*5J@=^B)9;C+VAqI|bWYte*TO1X)hBszlv7E_=Zm!; zPe*onRYGQ~x|0c+OirMfL!AJUj5S_(q`0!AA;o0o0!x)Cd)!Ymb+CJK_P!Fd9daHN zfp>1rop9(^&l$iuLI6kmFgV^*)3dQ5UA&U__gIdtT7EDa9Ql%+j3Jr>bYQ{*JsWQf(ePUNj|h&H}&O=aL-yz_cyse`dR7%X(?>XTL*qj zC5qEF!-?C#MHN;ae~8#Zr@&cmTj{zTEGJ8RI!F)eh*D0Zsz7B&3QN35t8AbyX*oR~ zf=hz&o1J)eq}B3#o<8}wU!0i!j{eC8=-8ZMK%BgpgRi46d6zf=SwDQUX!+`nlUfeN zy=@fytWmDxq;*(jzSu`VzBTk#qT*eL$*Dc^Z9?s$42@LvPy%gxSva%nO4zuaMGUyEazy9wZ zZuUd}0hw%=%8%}){{{d30T?sc58pkfdfIePJiq&&KRSymcdQ=}Er%u(_Y6JTdGq7; z1yT58GZUW8MsP?ize9QXXArf3w5k6yN-wAt{!|szjEoFDSZ9>eOI_)k^ikP@YBfks zS||(E%ewz>v8N))(0<5hH@^QmcOa5h`tL8le+PaC(e)^2?mH>%6nGKfZUa;KTczLy_6=<1s?pj<~F=s%;LbuNHfaV4)>5 z=62?Aw$L2YgObN#*4FmY(ZP^AVVCjF(?oe|;^=`T>FdVdo<=cUee2vkHNx)^%`XY_ zt_iij@qKFiBdgd??LEhb8qLQ@!5brLlT$-F-YoO+ej~WxoAaA2n4JNsoDS-P%6Pj0 zWh?ER-5lXB1MP4m{djOC<>`dQ4GuDK#i<;Lb1eI|l;h?IVJk-YUlSZq%#)GW$qDqs=w{{6w<9EyCKHNSh{MO}=D`7G|p_G)a z2V{EWSAZ|#>kGuEFt>`^ERk1>%X!0}cLqNr4N;Hi(M~^Dwxn%KP>UjiPp-vLnTMuA z323lm&Yt;Q-~?9pVMz6Cz8{sWACx_B4Z3KyYc!~d?R8m%^kBhrJ6k`@DK4*Ud9$AK z-X^rj!aRyWxg0Qh73F)K+SwN%#h!$5?1VV7tE^Q$+^#pFygIva@Qii8(ks85uCM0Q zuN0_#dBJ-EU|YG`=@QGv+H)Xar5Is|Ah@*757~0y#q9}lCL@_NP+r|1OUl`Yr^L97 zkZe=zNEtxQvSJ8;7i)C0$O_UEIQ!twzgzwn(XEzSMn=yFjLFo;rH|-5VTp!lu z%xU(ALpZ95)mtlA^G?|&>q28>b_W z(J0QX_U=%C2-Lw?gs017He=9mR9ce1Mb|1LB#i3;@1(yitLsLjk?C^3E-&qJz)c6- zn4e3RT4Z;FksQr;k41WA0skSF`BhGLb?`lA>(n zGY}u2Ebz&b$x`Es6&gS@_WX*Ae;>CaTJ}T<~TYpOLg%- zyd-y+R5%&%?_2jaL=`72XD+z_?`UtAZW6inm|6wkBCR2^!zx1lGr99 zmYxyD*NoUp0Y8)`4iZeTfjXG&qTHUH2GB#MW>A|SNWo^qkq3&xHu?kuu-dbGc#Qsq zE%KcS-=|wYa;hBy%Ww1Qih18VS4WKAOUlwnyhX~y(0Vg23q=bONG21JCaz=F+5x3` znqm>g8smw-2O1m5Co3Woo@WNUVzlx ztGV|M8({pVi;~Fa7uUtGlS^}XOc2$4wc_QmP8lDhHw2b z#VrA`Fp}9yQk?7%AS*Mirx;-l9`?$M?Rrk@t0UX#ajUOae76jNS}+yM1~nbb2 zRy&9O&Q^RFz0b?h_W*W9bsg(X36dN9XRi`K<7T-#3h%-#`q*}e{n4uD2BRW79`<1(O| z1S3~oh^;UZaG6 zjt{GSvCuCo92xjDvHk8m-LLVBo~Pdvo@ZU@pOxAjZ3qyH7~YLn1f>5b zG}2G5USUQZoMsT`5Xyy|{98&o^`{ZGRMBBO;ndVSg0Wf8L}qO5kG{)7_XEyN=z?BP zGG_&yDpao98v~uDRK2YjC9|}O8@Y_JWahLY95gJUPl)%Bo4;PrPeTMYmp3(*m0=h+ zyo{FDO$_9gE$xdkvSI>FnJ@AgmTh(X~3FAm*E(}+bLqbJMN(Tmr3$@Y+ zf8~s841iritH7V^AKwn~-7f7-LCJ#)?n3NNEm)<7IBD1rayhUr%sh`zvam@{`Vnf# zYR_HOtjnrZWVMh3*4xz#!l-Q};>COiI}w13$UA zEUk4GQp?cbvrplD8g5@F-8W-sN$OvpVB1 zj}ft~ocx{r2Pv#hxH5WKjrpgXPV)MTy@wTRk`J z#nc?oTxpNH1s21*J4a^PyxkMXin!9#AQ_{j8au4KL1j+Zje*fc=6HaQ7B9rmvE=+x zu!mV|=u<#|Y1htHAI$ecEX`qmi)nSD75bnv-{y|~&d%NB`PZhu4lf)g*Nf`kgvH0j zd<{g_#{u3u&rv=&gqkw8FQ#`{8I}Z?qD#RW9wsyr6p~IF%GHvwJ-r@L$#^;)rn@;N z4`XUs4j@(EObj|B_hm`XR?WdC>EULQe`~%%xb}`k2(*Y$@N4|cBf!i$yT;EmfSE!o zdRO4~-ssKinM2Fd`XnM3DN<%ThZW-WOhh8&Jh3LYwp~YM=?P2{u9Q)ZD0mX`6L#w~ zOs2_e5xZoiy7y!crS!>&z|!rVtvc-`NzNVw1G+? zh;0D^1VHF_IhMQi^2sWC-jp9dK66b%WbOI*ORh zDH;rhe9i9LLn+k@v?A-3D$UzGWulhSYr7sO8`Gf|OZrqk56G)s0NVl<<)dNytz7_e z!W%2$y@%RxBF{B-Fh{Yj-tWVj>=&&Eum2ihHLV;vMcfXIwhM#Y!>guiHc=5+dT-r# zHLW)(X!_u?fYy3^uiep&+-%*x;`{yu-Af%zBe;&Y;Pw3*Ao6{Cywe@`(fO_qbd9## zx1vLe>*Lp+Y>*us;;nf2zT+QBJXoV&-uTlIiK8rs?eEwK!A`$Nm_J_q37r^VO~Kqc z8-DQbkC*@T^4Sg&t=D|tKjudr0ocd1y2=~;`{!>k!ne&Nzkl?Gy9-<(_pXyZ)N@~& z`mj;6ZQHHtofxl(pl}Fx zHsg^tPyrWkAIr{U;rp|;#c_2M7(f7buOx>2W$Ws^@Q{~Wg*fU{`XWZD^RqSDzZqxmg zKzw^YYi%|)7yr0(WZ$pP*7yV}D^qPkUE+eNy1aV4?T z6l!(W^{GNrn?%%@op>od(T{<dP|L5b#{O%q`z(5_uaByx1?yf^1qXux3-N6Kls`sqAUFxU$ zUX@S=_~)%Tn6^iV4)aQ!pQ1!w*T)DcNTN-ph+QWGgR@995^#RU zP~)s2gd<%5_>XX@vNfZe?c}>1>Txx`xv$R|0hZEZMh#2}%z?KK-636~apBTOrV1=~ z64iUekLBff8H2wxbrgBDcc;=NX3uC99%pSl8p`-azzG|Wz@dp%ERQ4=VZbNK$wyff zxMmgXzq>EbL-+f>-7n2;+wKDBOU==)G7JVO82c5&6N zPHXX*u9?@Gzslcj^0yKhpt?Il2o%bPaRY`BWVQjw=_aM`FF%*=v+Vw|bRApk$Cv!F zcF&LewstAVbLheAJah0v-VHny;b0NCP^%ODzi!BhL}wQEbK?DXZ4#1q^x-BQZtL{>Vn6{Rgv`L$d#}JP;s+^fZP3GF%c$l6VkhaLK86XE+e)#XLbuOV zmJX?Wnc{$BgL-bYtQPccgaI$WlH8IW%M``Zkfz95<(INqmwR^mopyg5rMG+l;F3>W z^I}yU9Q?C)qj$-ki=+dnI+*ixCvrd5?UR=Vvh-<>KfmMW$I^D*9XJTLQ~HZ<1(n%A zl76eDg%kLexDpUrb+d^!1JGP)p-K31((dJPc(#K*hd~AH8WsQTcVl9V>(u(w|+COphV;)(~X?)m);< zibiuRcQkHR8CkB1cxxpQi6d%*BAKw4r!}sHBT+-d(>Rh4mkWhO68no(ADz^_1-d~W zMf-z`?eJp%q*{7m(%p*XZsUI;Q*V>#yr8@6d_7HYaN&XXrU*#5t39z|hl8buS-^3Z zI%PZ9jjez*8fc;SmgsSED%OcfJF>TS6q{jJ24KLe$H82xLleqor-9)600a#549MBB zo`5&6!lHWE^!W&Aed|R1Jp%CCOwvft|3?1HHY&(k!Ej=J3#dZF+5YIT6XU2KbE?di z9c0BGIcS?Qj4HyoT&37a2u9Z%UWei^89J=ir-i`g6v`eag{BLldt~|x3O5D)DBlOU z3Dx^=Eo?=PlM5xS=xSn?8aX&`W)gB9*N9cvL2BCe0Ni-7@c1ld%>6Ns&w z(x%)eJ`YrQhKn2$mK7&b31EGK@&p!nv0BhwFoN;DTVjL;sT`Sfox~GlwN&_qWwxV? zR-~oN%;wgXQ>=IhP`yWMt}?#3U`N}`LGXo2vg5s##HWxwph`jWTh_pj>(C|A11_Mi~wlWnz1*y3;)ZT9W)Lfd%PnYTBcc0ToEPc=hz?d=w~0w zfn4F8)U`Jz-a755sIS+9`j}?GWm3z>gh!j1fh_6;6tX%qU)wlxP-5+PNK~W$x4AD{ za#Y!}{FQj?2UT7`NDO7xZC3&`paCR63=h@CBr$`S!Rq6CBZU;1k&$t9Zgrcr*)cn5 zn!??2-@b>n*IF&GGTCEk3}}i;K%JDOfOyui?^r^|!JyaVQbKI^1F-gmrF>+mAv^t^ z2O1JYf5Tg*V|C>u%VXu?X_DIJs;|PXW(=>dbE5Rm(X2!G<3h7}4V8n-S)I{YNCU~_ z&g$biH{s9Cl*)kkKe?3i@~XG}p6kBz4WICeKbiM0TF7B`H+avF8-?ND@Y)PR-Jh92 zNka`TSilY%uE(!K<9emBxvhZMI3oJwZt73?q}3d6x0!7SYTQ#6qv9#G3b3ZC`gCKf zID0PD@MtytH1!({=%uYlOhwY~ zfnB=ea?Eb*OAYBGYu%7ah+l+m>a3m8nB;8jZm(YM@ZtzW5LYcxb{llhS)=ii=@OUO z@&59f&savN zn`eOUbV9|W%H_T`T`fCjRTFI7B$Cn=^t6$=92rUp^cVb+ET+20^#n?Z(QI}6F5vj+ zD=G5{aldiX8Gm>;48X&tBZ^hvR&4jKvpreCWMKjUycs&P%oZwhiHY2W6y;@W}rME9{QzQwUoANwleKO?{#5{ zI@g+qU8NUcV~tv@C3&30O6Yu_JCJitJDT1kr#W$51D|{`)o|rz>2HRo$-%3Fu0tpW zf$M6QP&*sSWdDSXNWYj+?fJfPJ7%;6FoTsVT<4x*>qwr?vxNYC^l?tiw&z_39K@@( zU-3Y5&Nyw+z!fC3poT4bB6b=>fRNFQfv{C%8@mX5E??%lIScpa?+OARqkZ_QaM3(E zEdS~Y_xauD{7+zp1+)I{r2kYy9d2=;^SizA-z^Y5n0tp~1ApwLjXA}S9aJcTI$UOn zD()7vb+{}_H5*A0C5(?5hM&wj<}c?NX!JnGvY&}=@3kTM=acPJD^^)}o4D(=5$&dgdI*gAJjFN@*`Wt=B;(|S&oCfu@018#ZrY66Ap$3# zRuWssVJBW{ZKRFAb46ZR`&SblwzA|C#>pp=Y`G$Hx$YNMcG@$*95@PRr_|#Mtk`;O z@2p0bgi8g7I(e3Ek{OHlhQ9Qbqq-Y|D9?J0pAYuY@7YlQ&OQ94xcGs~eCBgu*T0(2 z-n(Pn_m_Lhih*;4=PFs1xBL+wCmzB(z{w~|fR1R&y@jfaESb{F4TVH!mcXo2*{f1n&83`42ffZgRR%RFuxK3h}&<^wN&P*`4 zkY8+ZNKbw&_3t(=b{*gRF|0a7Ah)8*yHOj60{=y1t}mq5$CgLbd*x!pbw@)8Y6PQZ;12|)TKQ%FmM;ZJA()4xXzo^z5Qo(pJh<|=bKr)rRTRA z&i*5f_)@m~m!$Xet3ZeTq&FJ2$RiT15-V*XW$DNMaR1+ekk?{10?WjRVQFuQoj`lC zg(w7@>q)|=r)*1C(M~)|5y}Ip zZ9Jm6I`e9pN!JDt0=T5frzSm_=>!noZkDJ0^sgo{*vPN;$75nIJg0j+|8V^tUwl4R z(7J#oj-Maw;|s%u@%Z?s1-#>t{-tFkbuYA0-5r$^aj*Bna^pIzr&;=X?qV8u&=0L3 zcAl;6cRV_(A-X5c0&_E&bjFAL`S)8I7)bAW2!jQ9?fNSDvlh2I0H7vz&zuTUn{oB}MEY)) z&9+YGX6i0ng^leWGb1;bNZZJxV$AQUQI=yPDqxOhU~GWyRJdodgX^Y?$+vnnf0;)F z4&ooMyHcoQ4iU&>e-7Q?x5?di;RC`1*|&Qp>H{45D6>E1A@1L}_ZY%24@iWwAGyh$ zb_Rmwm+nyzw?%_>HjWZ}s>oWEcXiN*!O}T)XWAM^@RFn}z6cGsc7!k3Qoqg`A(8oT zmRwj`QoSVQd)AUSjVa{DNRrET|2*b@ILkNG1hk*tr*JpwpM8RNYz2JkwoPBVx%yPi zX~8nv*_OpwP%aHb>Drc)b7J_a6}7AN#Z|8=)so7U?YVNr9YF2GAKMkL9~O*4Y~qyn zLjg*MQ7*4mt`)Q&F8)`u@#89o|KLKp(MtnX^5&oYVKz5rh`*5z6xfTeS18_1FC;O; z*7UMO5!>4`{UNA(wjL$F=GAe!9gicm`I02?99l+r>Z6b&}ZB)eogi)jSO zT+Y4=7eBCMzq_J#4x%BD0ft7fH2!PU%ln=jxFG(Px=OV-T_1!A>&#FA*4`B2vQ)SNHar>X%Ss$nWNr5rLl9_E+nB|K8l+##KP-zh*&MdlB>P;!cw@`^fl%)_dvWQ2nb z+E&wB8%W&-XwKwZtyFBcl8FqxR^L4*0FDkW*wY99C7=1rBwlybJlHRrE2(!FW>gr< zp+c_)4MYW7BjycVW(}j`7AwP1yc4wuSJNGHPNCI*2h9m-optIoxdJ3syk1d*6=|c3 z(8;$n z9BACwoIn9nClM1_ut38V+qDvl6NP-fy z5$%I1TCvB-Scyx^?0sii(0R1L7RX_v3l_JL)>R?}Ya!%~YjZqqv)XL}F5HnBXlg$S zig3k08J@++g-`*+#=awZ9+;Fr^YH>j?~ee~0R9Hy1Gm%yjW@jY%0KKjV-WuctA)MA zQ9LQa>-sn}=+vjFK*hdrZc@dfmm?ElB8Najpn)$^8Xt|v9^oH%JVN!D91z-h zD^A!#@!RRn+lh&m`71Wvbg6+@4if^ii~roFU%6J`B|@9l&3%CDbnq3yuD@@vhn(_{ z_vV-3Vz|Ba+3_l%+Z;N*${#?uGE}XxUkU^-xyV6uDl}dXTq5vBZF4XgPS2Ya;KdV2 z=10+ z+zuk3W&!y4=X3b}$zMtyz$gF1jm!+vPd`8$?RF3$ee%oX|Fm8|fA3!D|K)2ht=X8< z>4=}Qsx_-+s!@F|BaAYMCIlO3{Wu`0%2O@P>=EmN10vVDoGP1{MfRSW`yS!R{COce zCtahrL?`VMU`Rrm=Wh9bv1VUvoT5wYTO4-h-YOv%1}6qK;on~mm%Aj3c6q?oXP35m z9ok#BKutUWDKY`n*&6zg)1|0SaDM|1Z$q^lHR2Y<83Cb5V=CcppQ%3X_0_~ zxuJCx1f~#qM%#tQa9&xF>kSVjpd#gw%Q6ed+5#2wnU1(?KRpm^GsZW8;prED4X_@e zz>>qb&XI3jtLIIB8!L!*{CHaP+ZFwqFV2bV^omtlw*VO|Cf_EEjU`at2M~@)u*N*K zXKQ~A?2-~QBTozu1n=z5yfFPb)b@p{SAoPH8{%ScAp3W@ z*d;dD7Zv`$YoH@wDk)Vh{|J5bNm9PqH;R$O{vI_$4$KRl2$N5^K*1 zp#{1z&%r()DWERjFX$?}UAz!oP;NTl;ru>ml&k(wnPCE(e(gxju4S=%0UjjDFdhTC@pAas<`cz&60(e2S< zJ;c?IrnhG#(sUc9MVm9e*H1s~>tDjz+W?1HBgta$)$Gu`z`uvo-r&T%=CSRF1}EJU zv)iQ=PoI#&5tj)wT}>)BCvjxdI#+Zt-wL?ASZLUYYo#sDnBYg!|z^j`I6VC24Zhm^j9+@^Zn9Nw`T_huhX==P*YAZ z8c;y*cH}zQo2E5HL(uWvAoJ_YGI#*3OwY<_-5-QLo^5*lsvl=-DLs15A;;adfr#3N zxBs=}djz<3biHkl*KG9VkR|dZtSpA`4i{%_A02(kN`NMo%qkEd=Ufp zz01&pmw>vZ+Oi-rkZh-_;mBk3eHN~~n4 zgYdHobV5SNYP(o;wm@z+S;oR)+qIQcG;6+STo+_0XT@$ohSg$TLz#BX zl^_7~6s=?R+KV(kn)ymfZ!Q6eJAR@jrpYuPFsmwBcXuIF7nH_RXf&7bvcehdqVCn;~7cL4;4LP zuWNyi97&$6E@D`@Ch)6mqyPk0HC)7;Nv=}gbI<~0)vTgN5%k_>*|?VPL_?eY`<*vb z&tZ*Un!=c$C3p|ru0#&aqf$(z`H?|A#IhrVy>7|!C^}1Hvc{OeS&TSyw~h67zUs7U z17fkXTiN2mtLGrxTb!e_U{PUf-f3$FhXHg=%H{&9=B4eypokZ*1dE621-h^+1<-lB zZ4L=f5_X?TtYu@H8@sic@8I}5n-YkFjKrkscB7kayJl4JY*5$cOrQJxb);}>BTt}= znw7*II{MR~2AlsnJ6&ONQ!=TtHY93%rQ_34(9av)MDvZ*q1ut+rPdG|Ae17z0$tBR zRiXB07$O5&XB$hWYe8ieJxl5}qjA9Pw9Rt;7yX~#KyU+yb*SRQrGN7g-rjzH(03^M zz4G&A!TV+>UUPS?#&BwMw%KOT`K6CH#!(~Wx~ukN8U#5RHQ|MbBQ#If9j&{Sl#{%K zljCc&S1}5h;QNcdp?iM`u9JE-gcyKfrw_#aS9StwTlbp&-A+7u@rRew6jCl9+TXWp z;(Zn1?K=+tsSG|G*hd=XRD=w zse071QmK?1#S=_E_AqRW$WTdXTLo<7Rn#OQCZSMd7N{VtpV9Ugp5O1$BDBS|kY%{7 z;4Nxs(j+u*88Ga%HX#LFD~Hua(-J~lq;zsER+)EVxM_^9lk#fpp&}9_Bsa&R{76D- zUdMIyvZy)W!1Lu^lWKs@YrB4CnCmmYK(_Tt|ESr1a;=*j^!tXkT%gK;F~4Lqb0QM$ z`LZ>%Xl-{f2Qh5YvOCXF(GA;XTX4F!HmIFI(%R$^Nmv`rdT*8+dZQ!dd1T~K*9aR0 zRoDQZ$(k}hxBQ1*{1?8+cZI22OL0*G@#cF+@gp;KKL>aA`8x&C!;lQ`{Lae$u@bk; zz)LHkpO))j9GdY~lG}#Z7eWGH0FWO(aTKs}u%ak;wWWOq#Z|7xRgA z$qt|ie8dk-?e?PmVH9#7;(?*W{{wUaW8VgI1x!>gJ+P(8$*XD86xwKMC%q?LHufgq ziQSmF;)ri#nA+)xhB4ALS4RQxj?+5HQXDfbb`xWXoT$c#qy$=onT-l9IM8ZpfAB$l zW#u6xw_Q-k{c*nSPe3R>Hs~KJ<^W^+5cS`*dd%+s##J+)kg**0r2mj(FkOs7jauRPBuTijdy5JGG+YkEV(*6bznqk#2=H5jrCk23J*W&C0Z-~NAmB*Qh|4%oxL;kAcMKMhHq)GWRX zW^>Zq#<=($K8wMzvKp+03WZbONYOIZ!ZFu0PPO-YS_*xVunmXFc ztfwGnvwX%yUo7;eg8T2L_gx46T`vCSu)OcV3UXHi*W|;G{dE7fF_g&Of!VKxEM$K2 z$D7@`Ijf6kw9G{CODS}H+NqFt351c@%2)C<1|64l3CYH$*Zajq(Z}&b<0quBbTo%@ zw;aXA&3P6(P~?x8kDip5d>kMP91h8Y`S|xKjc*`psD!>|l{-oFcfk%FWn1=Kt}dEB znRjRf5jhS$bX>d|c72M1anNY)ti6w`G)6j1P-kXGaJ&gd4n6jD3tdsvCY*`rG_l z1jS&Q9tY=wv&L zy4UevJJS5dyUIc%<=6cT+w0Q^Bqexkna?3####D~(OswF9Hl1OmXoG(CP>E_ z-UNU*TdO;-ipSy6u8|2QC1XhPp-yKX)JbJ%TQ_JxH3^c!wdF*o?P#HMc1b=D*(VFw znemWNhg;*d$C~4{2HoyQM(4@qGN!tEu1`-=I$HqUXk@E2GH(<>X(~lfYY4k3#uH~9 zLep?rWqHH%tHSCOYYWQ5nJyo(`F>KZzNMRc8#--<4cGzsXz|_|gFxZ@Rjf`rMFW&~ z?s^FPut#Q^#yhb8GB8;|--V>?34Z z+vQx1k6PvA%y)s$cdbD-lmj9gASweF<_B=eac^(A3&R0SE`>J{zbo z-Z9OOKm6xt{O82eufH~!oI9n=BXa+FyTU`g?brKLO<}-404P)at^E2aEb1cJGY$_o z?$RdOi`aB~%Jp2PE&S~cBrhutDUnz>U3s?=D*=oBZ6>b{{_!Gjn62AxCp#egJeiQs zB3JR=_-j)3;oSArP*lB}{(k{QA>_sRASXv{fo%=oT0bJ|>z;0kDW@r(g-z%-76UFf z63~9Qv=VZet(M)`0Zrdic&gJjKe-ra)H}(94;HASui~ZqE!#Tu^@0ogzA^t4YWSct z1OCzf_+S5%hR;5C%}x3ZUL3#|_g6pvz|SGy{YT!v{D)}oj>!1#GhaD%zv4LV8+L#P zeSvOyHyFRbTW){uS3^JKApZRy|3KhBU}kf_@Roi$N*|y5x3{5<@!#K`Z{D4mp{fb1 z_s_z){n~x0-fJ57O!)mU{c#NM<Zy@ literal 0 HcmV?d00001 diff --git a/apps/mobile/pnpm-workspace.yaml b/apps/mobile/pnpm-workspace.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx new file mode 100644 index 000000000..2995de696 --- /dev/null +++ b/apps/mobile/src/App.tsx @@ -0,0 +1,54 @@ +import { DefaultTheme, NavigationContainer, Theme } from '@react-navigation/native'; +import { StatusBar } from 'expo-status-bar'; +import React, { useEffect } from 'react'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { useDeviceContext } from 'twrnc'; + +import useCachedResources from './hooks/useCachedResources'; +import { getItemFromStorage } from './lib/storage'; +import tw from './lib/tailwind'; +import RootNavigator from './navigation'; +import OnboardingNavigator from './navigation/OnboardingNavigator'; +import { useOnboardingStore } from './stores/useOnboardingStore'; + +// +const NavigatorTheme: Theme = { + ...DefaultTheme, + colors: { + ...DefaultTheme.colors, + background: '#08090D' + } +}; + +export default function App() { + // Enables dark mode, and screen size breakpoints, etc. for tailwind + useDeviceContext(tw, { withDeviceColorScheme: false }); + + const isLoadingComplete = useCachedResources(); + + const { showOnboarding, hideOnboarding } = useOnboardingStore(); + + // Runs when the app is launched + useEffect(() => { + getItemFromStorage('@onboarding').then((value) => { + value && hideOnboarding(); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (!isLoadingComplete) { + return null; + } else { + return ( + + + + {showOnboarding ? : } + + + + + ); + } +} diff --git a/apps/mobile/src/assets/icons/file/index.ts b/apps/mobile/src/assets/icons/file/index.ts new file mode 100644 index 000000000..c67268de8 --- /dev/null +++ b/apps/mobile/src/assets/icons/file/index.ts @@ -0,0 +1,347 @@ +import ai from '@sd/assets/icons/ai.svg'; +import angular from '@sd/assets/icons/angular.svg'; +import audiomp3 from '@sd/assets/icons/audio-mp3.svg'; +import audioogg from '@sd/assets/icons/audio-ogg.svg'; +import audiowav from '@sd/assets/icons/audio-wav.svg'; +import audio from '@sd/assets/icons/audio.svg'; +import babel from '@sd/assets/icons/babel.svg'; +import bat from '@sd/assets/icons/bat.svg'; +import bicep from '@sd/assets/icons/bicep.svg'; +import binary from '@sd/assets/icons/binary.svg'; +import blade from '@sd/assets/icons/blade.svg'; +import browserslist from '@sd/assets/icons/browserslist.svg'; +import bsconfig from '@sd/assets/icons/bsconfig.svg'; +import bundler from '@sd/assets/icons/bundler.svg'; +import c from '@sd/assets/icons/c.svg'; +import cert from '@sd/assets/icons/cert.svg'; +import cheader from '@sd/assets/icons/cheader.svg'; +import cli from '@sd/assets/icons/cli.svg'; +import compodoc from '@sd/assets/icons/compodoc.svg'; +import composer from '@sd/assets/icons/composer.svg'; +import conf from '@sd/assets/icons/conf.svg'; +import cpp from '@sd/assets/icons/cpp.svg'; +import csharp from '@sd/assets/icons/csharp.svg'; +import cshtml from '@sd/assets/icons/cshtml.svg'; +import cssmap from '@sd/assets/icons/css-map.svg'; +import css from '@sd/assets/icons/css.svg'; +import csv from '@sd/assets/icons/csv.svg'; +import dartlang from '@sd/assets/icons/dartlang.svg'; +import dockerdebug from '@sd/assets/icons/docker-debug.svg'; +import dockerignore from '@sd/assets/icons/docker-ignore.svg'; +import docker from '@sd/assets/icons/docker.svg'; +import editorconfig from '@sd/assets/icons/editorconfig.svg'; +import eex from '@sd/assets/icons/eex.svg'; +import elixir from '@sd/assets/icons/elixir.svg'; +import elm from '@sd/assets/icons/elm.svg'; +import env from '@sd/assets/icons/env.svg'; +import erb from '@sd/assets/icons/erb.svg'; +import erlang from '@sd/assets/icons/erlang.svg'; +import eslint from '@sd/assets/icons/eslint.svg'; +import exs from '@sd/assets/icons/exs.svg'; +import exx from '@sd/assets/icons/exx.svg'; +import file from '@sd/assets/icons/file.svg'; +import folderlight from '@sd/assets/icons/folder-light.svg'; +import folderopen from '@sd/assets/icons/folder-open.svg'; +import folder from '@sd/assets/icons/folder.svg'; +import fontotf from '@sd/assets/icons/font-otf.svg'; +import fontttf from '@sd/assets/icons/font-ttf.svg'; +import fontwoff2 from '@sd/assets/icons/font-woff2.svg'; +import fontwoff from '@sd/assets/icons/font-woff.svg'; +import git from '@sd/assets/icons/git.svg'; +import gopackage from '@sd/assets/icons/go-package.svg'; +import go from '@sd/assets/icons/go.svg'; +import gradle from '@sd/assets/icons/gradle.svg'; +import graphql from '@sd/assets/icons/graphql.svg'; +import groovy from '@sd/assets/icons/groovy.svg'; +import grunt from '@sd/assets/icons/grunt.svg'; +import gulp from '@sd/assets/icons/gulp.svg'; +import haml from '@sd/assets/icons/haml.svg'; +import handlebars from '@sd/assets/icons/handlebars.svg'; +import haskell from '@sd/assets/icons/haskell.svg'; +import html from '@sd/assets/icons/html.svg'; +import imagegif from '@sd/assets/icons/image-gif.svg'; +import imageico from '@sd/assets/icons/image-ico.svg'; +import imagejpg from '@sd/assets/icons/image-jpg.svg'; +import imagepng from '@sd/assets/icons/image-png.svg'; +import imagewebp from '@sd/assets/icons/image-webp.svg'; +import image from '@sd/assets/icons/image.svg'; +import info from '@sd/assets/icons/info.svg'; +import ipynb from '@sd/assets/icons/ipynb.svg'; +import java from '@sd/assets/icons/java.svg'; +import jenkins from '@sd/assets/icons/jenkins.svg'; +import jest from '@sd/assets/icons/jest.svg'; +import jinja from '@sd/assets/icons/jinja.svg'; +import jsmap from '@sd/assets/icons/js-map.svg'; +import js from '@sd/assets/icons/js.svg'; +import json from '@sd/assets/icons/json.svg'; +import jsp from '@sd/assets/icons/jsp.svg'; +import julia from '@sd/assets/icons/julia.svg'; +import karma from '@sd/assets/icons/karma.svg'; +import key from '@sd/assets/icons/key.svg'; +import less from '@sd/assets/icons/less.svg'; +import license from '@sd/assets/icons/license.svg'; +import lighteditorconfig from '@sd/assets/icons/lighteditorconfig.svg'; +import liquid from '@sd/assets/icons/liquid.svg'; +import llvm from '@sd/assets/icons/llvm.svg'; +import log from '@sd/assets/icons/log.svg'; +import lua from '@sd/assets/icons/lua.svg'; +import m from '@sd/assets/icons/m.svg'; +import markdown from '@sd/assets/icons/markdown.svg'; +import mint from '@sd/assets/icons/mint.svg'; +import mov from '@sd/assets/icons/mov.svg'; +import mp4 from '@sd/assets/icons/mp4.svg'; +import nestjscontroller from '@sd/assets/icons/nestjs-controller.svg'; +import nestjsdecorator from '@sd/assets/icons/nestjs-decorator.svg'; +import nestjsfilter from '@sd/assets/icons/nestjs-filter.svg'; +import nestjsguard from '@sd/assets/icons/nestjs-guard.svg'; +import nestjsmodule from '@sd/assets/icons/nestjs-module.svg'; +import nestjsservice from '@sd/assets/icons/nestjs-service.svg'; +import nestjs from '@sd/assets/icons/nestjs.svg'; +import netlify from '@sd/assets/icons/netlify.svg'; +import nginx from '@sd/assets/icons/nginx.svg'; +import nim from '@sd/assets/icons/nim.svg'; +import njk from '@sd/assets/icons/njk.svg'; +import nodemon from '@sd/assets/icons/nodemon.svg'; +import npmlock from '@sd/assets/icons/npm-lock.svg'; +import npm from '@sd/assets/icons/npm.svg'; +import nuxt from '@sd/assets/icons/nuxt.svg'; +import nvm from '@sd/assets/icons/nvm.svg'; +import opengl from '@sd/assets/icons/opengl.svg'; +import pdf from '@sd/assets/icons/pdf.svg'; +import photoshop from '@sd/assets/icons/photoshop.svg'; +import php from '@sd/assets/icons/php.svg'; +import postcssconfig from '@sd/assets/icons/postcss-config.svg'; +import powershelldata from '@sd/assets/icons/powershell-data.svg'; +import powershellmodule from '@sd/assets/icons/powershell-module.svg'; +import powershell from '@sd/assets/icons/powershell.svg'; +import prettier from '@sd/assets/icons/prettier.svg'; +import prisma from '@sd/assets/icons/prisma.svg'; +import prolog from '@sd/assets/icons/prolog.svg'; +import pug from '@sd/assets/icons/pug.svg'; +import python from '@sd/assets/icons/python.svg'; +import qt from '@sd/assets/icons/qt.svg'; +import razor from '@sd/assets/icons/razor.svg'; +import reactjs from '@sd/assets/icons/react-js.svg'; +import reactts from '@sd/assets/icons/react-ts.svg'; +import readme from '@sd/assets/icons/readme.svg'; +import rescript from '@sd/assets/icons/rescript.svg'; +import rjson from '@sd/assets/icons/rjson.svg'; +import robots from '@sd/assets/icons/robots.svg'; +import rollup from '@sd/assets/icons/rollup.svg'; +import ruby from '@sd/assets/icons/ruby.svg'; +import rust from '@sd/assets/icons/rust.svg'; +import sass from '@sd/assets/icons/sass.svg'; +import scss from '@sd/assets/icons/scss.svg'; +import shell from '@sd/assets/icons/shell.svg'; +import smarty from '@sd/assets/icons/smarty.svg'; +import sol from '@sd/assets/icons/sol.svg'; +import sql from '@sd/assets/icons/sql.svg'; +import storybook from '@sd/assets/icons/storybook.svg'; +import stylelint from '@sd/assets/icons/stylelint.svg'; +import stylus from '@sd/assets/icons/stylus.svg'; +import svelte from '@sd/assets/icons/svelte.svg'; +import svg from '@sd/assets/icons/svg.svg'; +import swift from '@sd/assets/icons/swift.svg'; +import symfony from '@sd/assets/icons/symfony.svg'; +import tailwind from '@sd/assets/icons/tailwind.svg'; +import testjs from '@sd/assets/icons/test-js.svg'; +import testts from '@sd/assets/icons/test-ts.svg'; +import tmpl from '@sd/assets/icons/tmpl.svg'; +import toml from '@sd/assets/icons/toml.svg'; +import travis from '@sd/assets/icons/travis.svg'; +import tsconfig from '@sd/assets/icons/tsconfig.svg'; +import tsx from '@sd/assets/icons/tsx.svg'; +import twig from '@sd/assets/icons/twig.svg'; +import txt from '@sd/assets/icons/txt.svg'; +import typescriptdef from '@sd/assets/icons/typescript-def.svg'; +import typescript from '@sd/assets/icons/typescript.svg'; +import ui from '@sd/assets/icons/ui.svg'; +import user from '@sd/assets/icons/user.svg'; +import vercel from '@sd/assets/icons/vercel.svg'; +import video from '@sd/assets/icons/video.svg'; +import vite from '@sd/assets/icons/vite.svg'; +import vscode from '@sd/assets/icons/vscode.svg'; +import vue from '@sd/assets/icons/vue.svg'; +import wasm from '@sd/assets/icons/wasm.svg'; +import webpack from '@sd/assets/icons/webpack.svg'; +import windi from '@sd/assets/icons/windi.svg'; +import xml from '@sd/assets/icons/xml.svg'; +import yaml from '@sd/assets/icons/yaml.svg'; +import yarnerror from '@sd/assets/icons/yarn-error.svg'; +import yarn from '@sd/assets/icons/yarn.svg'; +import zip from '@sd/assets/icons/zip.svg'; + +export default { + ai, + angular, + audiomp3, + audioogg, + audiowav, + audio, + babel, + bat, + bicep, + binary, + blade, + browserslist, + bsconfig, + bundler, + c, + cert, + cheader, + cli, + compodoc, + composer, + conf, + cpp, + csharp, + cshtml, + cssmap, + css, + csv, + dartlang, + dockerdebug, + dockerignore, + docker, + editorconfig, + eex, + elixir, + elm, + env, + erb, + erlang, + eslint, + exs, + exx, + file, + folderlight, + folderopen, + folder, + fontotf, + fontttf, + fontwoff, + fontwoff2, + git, + gopackage, + go, + gradle, + graphql, + groovy, + grunt, + gulp, + haml, + handlebars, + haskell, + html, + imagegif, + imageico, + imagejpg, + imagepng, + imagewebp, + image, + info, + ipynb, + java, + jenkins, + jest, + jinja, + jsmap, + js, + json, + jsp, + julia, + karma, + key, + less, + license, + lighteditorconfig, + liquid, + llvm, + log, + lua, + m, + markdown, + mint, + mov, + mp4, + nestjscontroller, + nestjsdecorator, + nestjsfilter, + nestjsguard, + nestjsmodule, + nestjsservice, + nestjs, + netlify, + nginx, + nim, + njk, + nodemon, + npmlock, + npm, + nuxt, + nvm, + opengl, + pdf, + photoshop, + php, + postcssconfig, + powershelldata, + powershellmodule, + powershell, + prettier, + prisma, + prolog, + pug, + python, + qt, + razor, + reactjs, + reactts, + readme, + rescript, + rjson, + robots, + rollup, + ruby, + rust, + sass, + scss, + shell, + smarty, + sol, + sql, + storybook, + stylelint, + stylus, + svelte, + svg, + swift, + symfony, + tailwind, + testjs, + testts, + tmpl, + toml, + travis, + tsconfig, + tsx, + twig, + txt, + typescriptdef, + typescript, + ui, + user, + vercel, + video, + vite, + vscode, + vue, + wasm, + webpack, + windi, + xml, + yaml, + yarnerror, + yarn, + zip +}; diff --git a/apps/mobile/src/assets/temp/README.md b/apps/mobile/src/assets/temp/README.md new file mode 100644 index 000000000..f639a914b --- /dev/null +++ b/apps/mobile/src/assets/temp/README.md @@ -0,0 +1,3 @@ +Anything here is copied from some other app/package and should be moved to `assets` package for consistency. + +- Make sure sure to fix imports throughout the monorepo when you do such action. diff --git a/apps/mobile/src/assets/temp/folder-white.svg b/apps/mobile/src/assets/temp/folder-white.svg new file mode 100644 index 000000000..069ff8b01 --- /dev/null +++ b/apps/mobile/src/assets/temp/folder-white.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/mobile/src/assets/temp/folder.svg b/apps/mobile/src/assets/temp/folder.svg new file mode 100644 index 000000000..5b9be3c5e --- /dev/null +++ b/apps/mobile/src/assets/temp/folder.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/mobile/src/assets/temp/logo.png b/apps/mobile/src/assets/temp/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..37c70ec1be732b99b2519176b8e76405e8edfb16 GIT binary patch literal 129137 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/src/components/animation/layout.tsx b/apps/mobile/src/components/animation/layout.tsx new file mode 100644 index 000000000..78253f6c4 --- /dev/null +++ b/apps/mobile/src/components/animation/layout.tsx @@ -0,0 +1,101 @@ +import { MotiView, useDynamicAnimation } from 'moti'; +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { useDerivedValue, useSharedValue } from 'react-native-reanimated'; + +import Layout from '../../constants/Layout'; +import tw from '../../lib/tailwind'; + +// Anything wrapped with FadeIn will fade in on mount. +export const FadeInAnimation = ({ children, delay }: { children: any; delay?: number }) => ( + + {children} + +); + +export const FadeInUpAnimation = ({ children, delay }: { children: any; delay?: number }) => ( + + {children} + +); + +export const LogoAnimation = ({ children }: { children: any }) => ( + + {children} + +); + +type AnimatedHeightProps = { + children?: React.ReactNode; + /** + * If `true`, the height will automatically animate to 0. Default: `false`. + */ + hide?: boolean; + onHeightDidAnimate?: (height: number) => void; + initialHeight?: number; +} & React.ComponentProps; + +export function AnimatedHeight({ + children, + hide = false, + style, + delay = 0, + transition = { type: 'timing', delay }, + onHeightDidAnimate, + initialHeight = 0, + ...motiViewProps +}: AnimatedHeightProps) { + const measuredHeight = useSharedValue(initialHeight); + const state = useDynamicAnimation(() => { + return { + height: initialHeight, + opacity: !initialHeight || hide ? 0 : 1 + }; + }); + if ('state' in motiViewProps) { + console.warn('[AnimateHeight] state prop not supported'); + } + + useDerivedValue(() => { + let height = Math.ceil(measuredHeight.value); + if (hide) { + height = 0; + } + + state.animateTo({ + height, + opacity: !height || hide ? 0 : 1 + }); + }, [hide, measuredHeight]); + + return ( + + key === 'height' && onHeightDidAnimate(attemptedValue as number)) + } + style={[tw`overflow-hidden`, style]} + > + { + measuredHeight.value = nativeEvent.layout.height; + }} + > + {children} + + + ); +} diff --git a/apps/mobile/src/components/base/Button.tsx b/apps/mobile/src/components/base/Button.tsx new file mode 100644 index 000000000..3cc76eda4 --- /dev/null +++ b/apps/mobile/src/components/base/Button.tsx @@ -0,0 +1,58 @@ +import { VariantProps, cva } from 'class-variance-authority'; +import { MotiPressable, MotiPressableProps } from 'moti/interactions'; +import React, { useMemo } from 'react'; +import { Pressable, PressableProps } from 'react-native'; + +import tw from '../../lib/tailwind'; + +const button = cva(['border rounded-md items-center shadow-sm'], { + variants: { + variant: { + default: 'bg-gray-50 border-gray-100', + primary: ['bg-primary-600'], + gray: ['bg-gray-100 border-gray-200'] + }, + size: { + default: ['py-1', 'px-3'], + sm: ['py-1', 'px-2'], + lg: ['py-2', 'px-4'] + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } +}); + +type ButtonProps = VariantProps & PressableProps; + +export const Button: React.FC = ({ variant, size, ...props }) => { + const { style, ...otherProps } = props; + return ; +}; + +type AnimatedButtonProps = VariantProps & MotiPressableProps; + +export const AnimatedButton: React.FC = ({ variant, size, ...props }) => { + const { style, containerStyle, ...otherProps } = props; + return ( + + ({ hovered, pressed }) => { + 'worklet'; + + return { + opacity: hovered || pressed ? 0.7 : 1, + scale: hovered || pressed ? 0.97 : 1 + }; + }, + [] + )} + style={tw.style(button({ variant, size }), style as string)} + // MotiPressable acts differently than Pressable so containerStyle might need to used to achieve the same effect + containerStyle={containerStyle} + {...otherProps} + /> + ); +}; diff --git a/apps/mobile/src/components/browse/BrowseLocationItem.tsx b/apps/mobile/src/components/browse/BrowseLocationItem.tsx new file mode 100644 index 000000000..8d0ba86d2 --- /dev/null +++ b/apps/mobile/src/components/browse/BrowseLocationItem.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Pressable, Text, View } from 'react-native'; + +import tw from '../../lib/tailwind'; +import FolderIcon from '../icons/Folder'; + +interface BrowseLocationItemProps { + folderName: string; + onPress: () => void; +} + +const BrowseLocationItem: React.FC = (props) => { + const { folderName, onPress } = props; + return ( + + + + + {folderName} + + + + ); +}; + +export default BrowseLocationItem; diff --git a/apps/mobile/src/components/browse/BrowseTagItem.tsx b/apps/mobile/src/components/browse/BrowseTagItem.tsx new file mode 100644 index 000000000..2f8508008 --- /dev/null +++ b/apps/mobile/src/components/browse/BrowseTagItem.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { ColorValue, Pressable, Text, View } from 'react-native'; + +import tw from '../../lib/tailwind'; + +type BrowseTagItemProps = { + tagName: string; + tagColor: ColorValue; + onPress: () => void; +}; + +const BrowseTagItem: React.FC = (props) => { + const { tagName, tagColor, onPress } = props; + return ( + + + + + {tagName} + + + + ); +}; + +export default BrowseTagItem; diff --git a/apps/mobile/src/components/device/Device.tsx b/apps/mobile/src/components/device/Device.tsx new file mode 100644 index 000000000..3eb626d82 --- /dev/null +++ b/apps/mobile/src/components/device/Device.tsx @@ -0,0 +1,309 @@ +import { FilePath } from '@sd/core'; +import { Cloud, Desktop, DeviceMobileCamera, Laptop } from 'phosphor-react-native'; +import React from 'react'; +import { FlatList, Text, View } from 'react-native'; +import { LockClosedIcon } from 'react-native-heroicons/solid'; + +import tw from '../../lib/tailwind'; +import FileItem from '../file/FileItem'; + +export interface DeviceProps { + name: string; + size: string; + type: 'laptop' | 'desktop' | 'phone' | 'server'; + locations: Array<{ name: string; folder?: boolean; format?: string; icon?: string }>; +} + +const placeholderFileItems: FilePath[] = [ + { + is_dir: true, + date_created: '2020-01-01T00:00:00.000Z', + date_indexed: '2020-01-01T00:00:00.000Z', + date_modified: '2020-01-01T00:00:00.000Z', + extension: '', + file_id: 1, + id: 1, + key: null, + location_id: 1, + materialized_path: '', + name: 'Minecraft', + parent_id: 0, + key_id: null, + location: null, + file: { + id: 1, + key_id: 1, + albums: [], + comments: [], + key: { + algorithm: null, + checksum: '', + date_created: null, + file_paths: [], + files: [], + id: 1, + name: 'Hello world' + }, + labels: [], + media_data: null, + spaces: [], + tags: [], + cas_id: '', + ipfs_id: '', + has_thumbnail: false, + favorite: false, + has_thumbstrip: false, + has_video_preview: false, + hidden: false, + important: false, + integrity_checksum: '', + kind: 1, + note: '', + paths: [], + size_in_bytes: '555', + date_created: '', + date_indexed: '', + date_modified: '' + } + }, + { + is_dir: true, + date_created: '2020-01-01T00:00:00.000Z', + date_indexed: '2020-01-01T00:00:00.000Z', + date_modified: '2020-01-01T00:00:00.000Z', + extension: '', + file_id: 2, + id: 2, + key: null, + location_id: 2, + materialized_path: '', + name: 'Documents', + parent_id: 0, + key_id: null, + location: null, + file: { + id: 2, + key_id: 2, + albums: [], + comments: [], + key: { + algorithm: null, + checksum: '', + date_created: null, + file_paths: [], + files: [], + id: 1, + name: 'Hello world' + }, + labels: [], + media_data: null, + spaces: [], + tags: [], + cas_id: '', + ipfs_id: '', + has_thumbnail: false, + favorite: false, + has_thumbstrip: false, + has_video_preview: false, + hidden: false, + important: false, + integrity_checksum: '', + kind: 1, + note: '', + paths: [], + size_in_bytes: '555', + date_created: '', + date_indexed: '', + date_modified: '' + } + }, + { + is_dir: false, + date_created: '2020-01-01T00:00:00.000Z', + date_indexed: '2020-01-01T00:00:00.000Z', + date_modified: '2020-01-01T00:00:00.000Z', + extension: 'tsx', + file_id: 3, + id: 3, + key: null, + location_id: 3, + materialized_path: '', + name: 'App.tsx', + parent_id: 0, + key_id: null, + location: null, + file: { + id: 3, + key_id: 3, + albums: [], + comments: [], + key: { + algorithm: null, + checksum: '', + date_created: null, + file_paths: [], + files: [], + id: 1, + name: 'Hello world' + }, + labels: [], + media_data: null, + spaces: [], + tags: [], + cas_id: '', + ipfs_id: '', + has_thumbnail: false, + favorite: false, + has_thumbstrip: false, + has_video_preview: false, + hidden: false, + important: false, + integrity_checksum: '', + kind: 1, + note: '', + paths: [], + size_in_bytes: '555', + date_created: '', + date_indexed: '', + date_modified: '' + } + }, + { + is_dir: false, + date_created: '2020-01-01T00:00:00.000Z', + date_indexed: '2020-01-01T00:00:00.000Z', + date_modified: '2020-01-01T00:00:00.000Z', + extension: 'vite', + file_id: 4, + id: 4, + key: null, + location_id: 4, + materialized_path: '', + name: 'vite.config.js', + parent_id: 0, + key_id: null, + location: null, + file: { + id: 4, + key_id: 4, + albums: [], + comments: [], + key: { + algorithm: null, + checksum: '', + date_created: null, + file_paths: [], + files: [], + id: 1, + name: 'Hello world' + }, + labels: [], + media_data: null, + spaces: [], + tags: [], + cas_id: '', + ipfs_id: '', + has_thumbnail: false, + favorite: false, + has_thumbstrip: false, + has_video_preview: false, + hidden: false, + important: false, + integrity_checksum: '', + kind: 1, + note: '', + paths: [], + size_in_bytes: '555', + date_created: '', + date_indexed: '', + date_modified: '' + } + }, + { + is_dir: false, + date_created: '2020-01-01T00:00:00.000Z', + date_indexed: '2020-01-01T00:00:00.000Z', + date_modified: '2020-01-01T00:00:00.000Z', + extension: 'docker', + file_id: 5, + id: 5, + key: null, + location_id: 5, + materialized_path: '', + name: 'Dockerfile', + parent_id: 0, + key_id: null, + location: null, + file: { + id: 5, + key_id: 5, + albums: [], + comments: [], + key: { + algorithm: null, + checksum: '', + date_created: null, + file_paths: [], + files: [], + id: 1, + name: 'Hello world' + }, + labels: [], + media_data: null, + spaces: [], + tags: [], + cas_id: '', + ipfs_id: '', + has_thumbnail: false, + favorite: false, + has_thumbstrip: false, + has_video_preview: false, + hidden: false, + important: false, + integrity_checksum: '', + kind: 1, + note: '', + paths: [], + size_in_bytes: '555', + date_created: '', + date_indexed: '', + date_modified: '' + } + } +]; + +const Device = ({ name, locations, size, type }: DeviceProps) => { + return ( + + + + {type === 'phone' && ( + + )} + {type === 'laptop' && } + {type === 'desktop' && } + {type === 'server' && } + {name || 'Unnamed Device'} + {/* P2P Lock */} + + + P2P + + + {/* Size */} + {size} + + {/* Locations/Files TODO: Maybe use FlashList? */} + } + keyExtractor={(item) => item.id.toString()} + horizontal + contentContainerStyle={tw`mt-4 ml-2`} + showsHorizontalScrollIndicator={false} + /> + + ); +}; + +export default Device; diff --git a/apps/mobile/src/components/drawer/DrawerContent.tsx b/apps/mobile/src/components/drawer/DrawerContent.tsx new file mode 100644 index 000000000..9f57c21d9 --- /dev/null +++ b/apps/mobile/src/components/drawer/DrawerContent.tsx @@ -0,0 +1,64 @@ +import { DrawerContentScrollView } from '@react-navigation/drawer'; +import { DrawerContentComponentProps } from '@react-navigation/drawer/lib/typescript/src/types'; +import { House } from 'phosphor-react-native'; +import React from 'react'; +import { Pressable, Text, View } from 'react-native'; +import { CogIcon } from 'react-native-heroicons/solid'; + +import Layout from '../../constants/Layout'; +import tw from '../../lib/tailwind'; +import type { DrawerNavParamList } from '../../navigation/DrawerNavigator'; +import { valueof } from '../../types/helper'; +import DrawerItem from './DrawerItem'; + +const drawerHeight = Layout.window.height * 0.85; + +// This is a hacky way to get the active route name and params but it works and it's typed... + +interface ActiveRoute { + key: string; + name: keyof DrawerNavParamList; + params: valueof>; +} + +const getActiveRouteState = function (state: any): ActiveRoute { + if (!state.routes || state.routes.length === 0 || state.index >= state.routes.length) { + return state; + } + + const childActiveRoute = state.routes[state.index]; + return getActiveRouteState(childActiveRoute); +}; + +// Overriding the default to add typing for our params. +// interface DrawerContentComponentProps { +// state: DrawerNavigationState; +// navigation: NavigationHelpers & +// DrawerActionHelpers; +// // descriptors type is generic +// descriptors: DrawerDescriptorMap; +// } + +const DrawerContent = ({ descriptors, navigation, state }: DrawerContentComponentProps) => { + return ( + + + + TODO: Library Selection + } + onPress={() => navigation.jumpTo('Home')} + isSelected={getActiveRouteState(state).name === 'Home'} + /> + + {/* Settings */} + navigation.navigate('Settings')}> + + + + + ); +}; + +export default DrawerContent; diff --git a/apps/mobile/src/components/drawer/DrawerItem.tsx b/apps/mobile/src/components/drawer/DrawerItem.tsx new file mode 100644 index 000000000..99daf69e9 --- /dev/null +++ b/apps/mobile/src/components/drawer/DrawerItem.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Pressable, Text, View } from 'react-native'; + +import tw from '../../lib/tailwind'; + +interface DrawerProps { + label: string; + onPress: () => void; + icon: JSX.Element; + isSelected: boolean; +} + +const DrawerItem: React.FC = (props) => { + const { label, icon, onPress, isSelected } = props; + return ( + + + {icon} + + {label} + + + + ); +}; + +export default DrawerItem; diff --git a/apps/mobile/src/components/drawer/DrawerScreenWrapper.tsx b/apps/mobile/src/components/drawer/DrawerScreenWrapper.tsx new file mode 100644 index 000000000..3bc677b40 --- /dev/null +++ b/apps/mobile/src/components/drawer/DrawerScreenWrapper.tsx @@ -0,0 +1,31 @@ +import { useDrawerProgress } from '@react-navigation/drawer'; +import React from 'react'; +import Animated, { Extrapolate, interpolate, useAnimatedStyle } from 'react-native-reanimated'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +import tw from '../../lib/tailwind'; + +// NOTE: Only wrap screens that need the drawer to be open. (Only Overview Screen for now.) +const DrawerScreenWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const progress: any = useDrawerProgress(); + + const style = useAnimatedStyle(() => { + // TODO: Fix this, it looks weird. Especially on Android. translateX/Y might be the cause. + const scale = interpolate(progress.value, [0, 1], [1, 0.88], Extrapolate.CLAMP); + const translateX = interpolate(progress.value, [0, 1], [0, -12], Extrapolate.CLAMP); + const translateY = interpolate(progress.value, [0, 1], [0, 12], Extrapolate.CLAMP); + const borderRadius = interpolate(progress.value, [0, 1], [0, 16], Extrapolate.CLAMP); + return { + transform: [{ scale }, { translateX }, { translateY }], + borderRadius + }; + }, []); + + return ( + + {children} + + ); +}; + +export default DrawerScreenWrapper; diff --git a/apps/mobile/src/components/file/FileItem.tsx b/apps/mobile/src/components/file/FileItem.tsx new file mode 100644 index 000000000..629fc489e --- /dev/null +++ b/apps/mobile/src/components/file/FileItem.tsx @@ -0,0 +1,77 @@ +import { FilePath } from '@sd/core'; +import React from 'react'; +import { Text, View } from 'react-native'; +import Svg, { Path } from 'react-native-svg'; + +import icons from '../../assets/icons/file'; +import tw from '../../lib/tailwind'; +import FolderIcon from '../icons/Folder'; + +type FileItemProps = { + file?: FilePath | null; +}; + +// TODO: Menu for file actions (File details, Share etc.) +// TODO: Sheet Modal for file details + +const FileItem = ({ file }: FileItemProps) => { + return ( + + + {/* Folder Icons/Thumbnail etc. */} + + {file?.is_dir ? ( + + + + ) : file?.file?.has_thumbnail ? ( + <>{/* TODO */} + ) : ( + + + + + + + + {/* File Icon & Extension */} + + {file?.extension && icons[file.extension] ? ( + (() => { + const Icon = icons[file.extension]; + return ; + })() + ) : ( + <> + )} + + {file?.extension} + + + + )} + + {/* Name */} + + + {file?.name} + + + + + ); +}; + +export default FileItem; diff --git a/apps/mobile/src/components/icons/Folder.tsx b/apps/mobile/src/components/icons/Folder.tsx new file mode 100644 index 000000000..0fa11688f --- /dev/null +++ b/apps/mobile/src/components/icons/Folder.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { SvgProps } from 'react-native-svg'; + +import FolderWhite from '../../assets/temp/folder-white.svg'; +import Folder from '../../assets/temp/folder.svg'; + +type FolderProps = { + /** + * Render a white folder icon + */ + isWhite?: boolean; + + /** + * The size of the icon to show -- uniform width and height + */ + size?: number; +} & SvgProps; + +const FolderIcon: React.FC = ({ size = 24, isWhite, ...svgProps }) => { + return isWhite ? ( + + ) : ( + + ); +}; + +export default FolderIcon; diff --git a/apps/mobile/src/components/layout/Card.tsx b/apps/mobile/src/components/layout/Card.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/apps/mobile/src/components/layout/CollapsibleView.tsx b/apps/mobile/src/components/layout/CollapsibleView.tsx new file mode 100644 index 000000000..abdf28a68 --- /dev/null +++ b/apps/mobile/src/components/layout/CollapsibleView.tsx @@ -0,0 +1,46 @@ +import { Ionicons } from '@expo/vector-icons'; +import { MotiView } from 'moti'; +import React, { useReducer } from 'react'; +import { Pressable, StyleProp, Text, TextStyle, View, ViewStyle } from 'react-native'; + +import tw from '../../lib/tailwind'; +import { AnimatedHeight } from '../animation/layout'; + +type CollapsibleViewProps = { + title: string; + titleStyle?: StyleProp; + children: React.ReactNode; + containerStyle?: StyleProp; +}; + +const CollapsibleView = ({ title, titleStyle, containerStyle, children }: CollapsibleViewProps) => { + const [hide, toggle] = useReducer((hide) => !hide, false); + + return ( + + + + {title} + + + + + + {children} + + ); +}; + +export default CollapsibleView; diff --git a/apps/mobile/src/components/layout/VirtualizedListWrapper.tsx b/apps/mobile/src/components/layout/VirtualizedListWrapper.tsx new file mode 100644 index 000000000..6a110b358 --- /dev/null +++ b/apps/mobile/src/components/layout/VirtualizedListWrapper.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { FlatList } from 'react-native'; + +export default function VirtualizedListWrapper({ children }) { + return ( + 'key'} + showsHorizontalScrollIndicator={false} + showsVerticalScrollIndicator={false} + renderItem={null} + ListHeaderComponent={<>{children}} + /> + ); +} diff --git a/apps/mobile/src/components/modals/FileDetails.tsx b/apps/mobile/src/components/modals/FileDetails.tsx new file mode 100644 index 000000000..1f4c77b84 --- /dev/null +++ b/apps/mobile/src/components/modals/FileDetails.tsx @@ -0,0 +1 @@ +// Only mount this modal if `FileItem` gets used in screen. diff --git a/apps/mobile/src/constants/Layout.ts b/apps/mobile/src/constants/Layout.ts new file mode 100644 index 000000000..2134acd31 --- /dev/null +++ b/apps/mobile/src/constants/Layout.ts @@ -0,0 +1,11 @@ +import { Dimensions } from 'react-native'; + +const { width, height } = Dimensions.get('window'); + +export default { + window: { + width, + height + }, + isSmallDevice: width < 375 +}; diff --git a/apps/mobile/src/containers/OverviewStats.tsx b/apps/mobile/src/containers/OverviewStats.tsx new file mode 100644 index 000000000..d117d3938 --- /dev/null +++ b/apps/mobile/src/containers/OverviewStats.tsx @@ -0,0 +1,50 @@ +import { Statistics } from '@sd/core'; +import byteSize from 'byte-size'; +import React from 'react'; +import { ScrollView, Text, View } from 'react-native'; + +import useCounter from '../hooks/useCounter'; +import tw from '../lib/tailwind'; + +const StatItemNames: Partial> = { + total_bytes_capacity: 'Total capacity', + preview_media_bytes: 'Preview media', + library_db_size: 'Index size', + total_bytes_free: 'Free space' +}; + +type OverviewStatsProps = { + stats: Statistics | undefined; +}; + +const StatItem: React.FC<{ title: string; bytes: number }> = ({ title, bytes }) => { + const { value, unit } = byteSize(+bytes); + + const count = useCounter({ name: title, end: Number(value) }); + + return ( + + {title} + + {count} + {unit} + + + ); +}; + +const OverviewStats = ({ stats }: OverviewStatsProps) => { + // TODO: Show missing library warning if stats is undefined + const displayableStatItems = Object.keys(StatItemNames) as unknown as keyof typeof StatItemNames; + + return stats ? ( + + {Object.entries(stats).map(([key, bytes]) => { + if (!displayableStatItems.includes(key)) return null; + return ; + })} + + ) : null; +}; + +export default OverviewStats; diff --git a/apps/mobile/src/hooks/useCachedResources.ts b/apps/mobile/src/hooks/useCachedResources.ts new file mode 100644 index 000000000..50770266b --- /dev/null +++ b/apps/mobile/src/hooks/useCachedResources.ts @@ -0,0 +1,47 @@ +import { FontAwesome } from '@expo/vector-icons'; +import * as Font from 'expo-font'; +import * as SplashScreen from 'expo-splash-screen'; +import { useEffect, useState } from 'react'; +import { Platform } from 'react-native'; + +/* + https://github.com/facebook/hermes/issues/23 + + We are using "Hermes" on Android & IOS, which for the current version (0.11), + IOS does not support the Intl fully so we need pollyfill it. + + NOTE: We can be picky about what we "pollyfill" to optimize but for now this works. +*/ + +if (Platform.OS === 'ios') { + require('intl'); // import intl object + require('intl/locale-data/jsonp/en'); +} + +export default function useCachedResources() { + const [isLoadingComplete, setLoadingComplete] = useState(false); + + // Load any resources or data that we need prior to rendering the app + useEffect(() => { + async function loadResourcesAndDataAsync() { + try { + SplashScreen.preventAutoHideAsync(); + + // Load fonts, icons etc. + await Font.loadAsync({ + ...FontAwesome.font + }); + } catch (e) { + // We might want to provide this error information to an error reporting service + console.warn(e); + } finally { + setLoadingComplete(true); + SplashScreen.hideAsync(); + } + } + + loadResourcesAndDataAsync(); + }, []); + + return isLoadingComplete; +} diff --git a/apps/mobile/src/hooks/useCounter.ts b/apps/mobile/src/hooks/useCounter.ts new file mode 100644 index 000000000..7b1f22289 --- /dev/null +++ b/apps/mobile/src/hooks/useCounter.ts @@ -0,0 +1,71 @@ +import { useEffect } from 'react'; +import { useCountUp } from 'use-count-up'; +import create from 'zustand'; + +const useCounterStore = create<{ + counterLastValue: Map; + setCounterLastValue: (key: string, value: number) => void; +}>((set) => ({ + counterLastValue: new Map(), + setCounterLastValue: (name, lastValue) => + set((state) => ({ + ...state, + counterLastValue: state.counterLastValue.set(name, lastValue) + })) +})); + +const useCounterState = (key: string) => { + const { counterLastValue, setCounterLastValue } = useCounterStore(); + + return { + lastValue: counterLastValue.get(key), + setLastValue: setCounterLastValue + }; +}; + +type UseCounterProps = { + name: string; + start?: number; + end: number; + /** + * Duration of the counter animation in seconds + * default: `2s` + */ + duration?: number; + /** + * If `true`, counter will only count up/down once per app session. + * default: `true` + */ + saveState?: boolean; +}; + +const useCounter = ({ name, start = 0, end, duration = 2, saveState = true }: UseCounterProps) => { + const { lastValue, setLastValue } = useCounterState(name); + + if (saveState && lastValue) { + start = lastValue; + } + + const { value } = useCountUp({ + isCounting: !(start === end), + start, + end, + duration, + easing: 'easeOutCubic' + }); + + useEffect(() => { + if (saveState && Number(value) === end) { + setLastValue(name, end); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [end, value]); + + if (start === end) return end; + + if (saveState && lastValue && lastValue === end) return end; + + return value; +}; + +export default useCounter; diff --git a/apps/mobile/src/lib/storage.ts b/apps/mobile/src/lib/storage.ts new file mode 100644 index 000000000..67bfa9e23 --- /dev/null +++ b/apps/mobile/src/lib/storage.ts @@ -0,0 +1,71 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; + +export const setItemToStorage = async (key: string, value: string | null) => { + if (value === null) return false; + try { + await AsyncStorage.setItem(key, value); + return true; + } catch (e: any) { + // saving error + console.log('Error', e); + return false; + } +}; + +export const getItemFromStorage = async (key: string) => { + try { + const value = await AsyncStorage.getItem(key); + if (value !== null) { + return value; + } + return undefined; + } catch (e: any) { + console.log('Error', e); + + return undefined; + } +}; + +// For Objects + +export const setObjStorage = async (key: string, value: any) => { + try { + await AsyncStorage.setItem(key, JSON.stringify(value)); + return true; + } catch (e: any) { + // saving error + console.log('Error', e); + + return false; + } +}; + +export const getObjFromStorage = async (key: string) => { + try { + const value = await AsyncStorage.getItem(key); + if (value !== null) { + return JSON.parse(value); + } + return null; + } catch (e: any) { + // error reading value + console.log('Error', e); + + return null; + } +}; + +export async function removeFromStorage(key: string) { + try { + await AsyncStorage.removeItem(key); + } catch (e: any) { + // remove error + console.log('Error', e); + } +} + +// We also have Merge, getAllKeys, Multimerge / Multiget etc.. +// https://react-native-async-storage.github.io/async-storage/docs/api + +// Safe Storage +// ??? diff --git a/apps/mobile/src/lib/tailwind.js b/apps/mobile/src/lib/tailwind.js new file mode 100644 index 000000000..4589b8528 --- /dev/null +++ b/apps/mobile/src/lib/tailwind.js @@ -0,0 +1,5 @@ +import { create } from 'twrnc'; + +const tw = create(require(`../../tailwind.config.js`)); + +export default tw; diff --git a/apps/mobile/src/navigation/DrawerNavigator.tsx b/apps/mobile/src/navigation/DrawerNavigator.tsx new file mode 100644 index 000000000..f1bdd7c8d --- /dev/null +++ b/apps/mobile/src/navigation/DrawerNavigator.tsx @@ -0,0 +1,48 @@ +import { DrawerScreenProps, createDrawerNavigator } from '@react-navigation/drawer'; +import { + CompositeScreenProps, + NavigatorScreenParams, + getFocusedRouteNameFromRoute +} from '@react-navigation/native'; +import { NativeStackScreenProps } from '@react-navigation/native-stack'; + +import type { RootStackParamList } from '.'; +import DrawerContent from '../components/drawer/DrawerContent'; +import type { TabParamList } from './TabNavigator'; +import TabNavigator from './TabNavigator'; + +const Drawer = createDrawerNavigator(); + +export default function DrawerNavigator() { + return ( + { + return { + headerShown: false, + drawerStyle: { + backgroundColor: '#08090D', + width: '75%' + }, + overlayColor: 'transparent', + // Can only swipe on Overview screen! (for opening drawer) + swipeEnabled: getFocusedRouteNameFromRoute(route) === 'Overview' + // drawerHideStatusBarOnOpen: true, + // drawerStatusBarAnimation: 'slide' + }; + }} + drawerContent={(props) => } + > + + + ); +} + +export type DrawerNavParamList = { + Home: NavigatorScreenParams | undefined; +}; + +export type HomeDrawerScreenProps = CompositeScreenProps< + DrawerScreenProps, + NativeStackScreenProps +>; diff --git a/apps/mobile/src/navigation/LinkingConfiguration.ts b/apps/mobile/src/navigation/LinkingConfiguration.ts new file mode 100644 index 000000000..fc60ec559 --- /dev/null +++ b/apps/mobile/src/navigation/LinkingConfiguration.ts @@ -0,0 +1,27 @@ +/** + * Learn more about deep linking with React Navigation + * https://reactnavigation.org/docs/deep-linking + * https://reactnavigation.org/docs/configuring-links + */ +import { LinkingOptions } from '@react-navigation/native'; +import * as Linking from 'expo-linking'; + +import { RootStackParamList } from '.'; + +// TODO: Deep linking for React Navigation. It will allow us to do spacedrive://tags/{id} etc. +const linking: LinkingOptions = { + prefixes: [Linking.createURL('/')], + config: { + screens: { + Root: { + screens: { + Home: 'home' + } + }, + Settings: 'settings', + NotFound: '*' + } + } +}; + +export default linking; diff --git a/apps/mobile/src/navigation/OnboardingNavigator.tsx b/apps/mobile/src/navigation/OnboardingNavigator.tsx new file mode 100644 index 000000000..e911c497e --- /dev/null +++ b/apps/mobile/src/navigation/OnboardingNavigator.tsx @@ -0,0 +1,20 @@ +import { NativeStackScreenProps, createNativeStackNavigator } from '@react-navigation/native-stack'; + +import OnboardingScreen from '../screens/onboarding/Onboarding'; + +const OnboardingStack = createNativeStackNavigator(); + +export default function OnboardingNavigator() { + return ( + + + + ); +} + +export type OnboardingStackParamList = { + Onboarding: undefined; +}; + +export type OnboardingStackScreenProps = + NativeStackScreenProps; diff --git a/apps/mobile/src/navigation/TabNavigator.tsx b/apps/mobile/src/navigation/TabNavigator.tsx new file mode 100644 index 000000000..be37f010c --- /dev/null +++ b/apps/mobile/src/navigation/TabNavigator.tsx @@ -0,0 +1,84 @@ +import { BottomTabScreenProps, createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { CompositeScreenProps } from '@react-navigation/native'; +import { CirclesFour, Folder, Planet } from 'phosphor-react-native'; +import { PhotographIcon } from 'react-native-heroicons/outline'; + +import tw from '../lib/tailwind'; +import OverviewScreen from '../screens/Overview'; +import PhotosScreen from '../screens/Photos'; +import SpacesScreen from '../screens/Spaces'; +import type { HomeDrawerScreenProps } from './DrawerNavigator'; +import BrowseStack from './tabs/BrowseStack'; + +const Tab = createBottomTabNavigator(); + +export default function TabNavigator() { + return ( + + ( + + ) + }} + /> + ( + + ), + tabBarLabel: 'Browse' + }} + /> + ( + + ) + }} + /> + ( + + ) + }} + /> + + ); +} + +export type TabParamList = { + Overview: undefined; + BrowseStack: undefined; + Spaces: undefined; + Photos: undefined; +}; + +export type TabScreenProps = CompositeScreenProps< + BottomTabScreenProps, + HomeDrawerScreenProps<'Home'> +>; diff --git a/apps/mobile/src/navigation/index.tsx b/apps/mobile/src/navigation/index.tsx new file mode 100644 index 000000000..24a9aad3e --- /dev/null +++ b/apps/mobile/src/navigation/index.tsx @@ -0,0 +1,34 @@ +import { NavigatorScreenParams } from '@react-navigation/native'; +import { NativeStackScreenProps, createNativeStackNavigator } from '@react-navigation/native-stack'; + +import NotFoundScreen from '../screens/NotFound'; +import SettingsScreen from '../screens/modals/settings/Settings'; +import DrawerNavigator from './DrawerNavigator'; +import type { DrawerNavParamList } from './DrawerNavigator'; + +const Stack = createNativeStackNavigator(); + +// This is the main navigator we nest everything under. +export default function RootNavigator() { + return ( + + + + + + + + ); +} + +export type RootStackParamList = { + Root: NavigatorScreenParams | undefined; + NotFound: undefined; + // Modals + Settings: undefined; +}; + +export type RootStackScreenProps = NativeStackScreenProps< + RootStackParamList, + Screen +>; diff --git a/apps/mobile/src/navigation/tabs/BrowseStack.tsx b/apps/mobile/src/navigation/tabs/BrowseStack.tsx new file mode 100644 index 000000000..95cac16fe --- /dev/null +++ b/apps/mobile/src/navigation/tabs/BrowseStack.tsx @@ -0,0 +1,30 @@ +import { CompositeScreenProps } from '@react-navigation/native'; +import { NativeStackScreenProps, createNativeStackNavigator } from '@react-navigation/native-stack'; + +import BrowseScreen from '../../screens/Browse'; +import LocationScreen from '../../screens/Location'; +import TagScreen from '../../screens/Tag'; +import { TabScreenProps } from '../TabNavigator'; + +const Stack = createNativeStackNavigator(); + +export default function BrowseStack() { + return ( + + + + + + ); +} + +export type BrowseStackParamList = { + Browse: undefined; + Location: { id: number }; + Tag: { id: number }; +}; + +export type BrowseScreenProps = CompositeScreenProps< + NativeStackScreenProps, + TabScreenProps<'BrowseStack'> +>; diff --git a/apps/mobile/src/screens/Browse.tsx b/apps/mobile/src/screens/Browse.tsx new file mode 100644 index 000000000..63657b416 --- /dev/null +++ b/apps/mobile/src/screens/Browse.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { ColorValue, Text, View } from 'react-native'; + +import BrowseLocationItem from '../components/browse/BrowseLocationItem'; +import BrowseTagItem from '../components/browse/BrowseTagItem'; +import CollapsibleView from '../components/layout/CollapsibleView'; +import tw from '../lib/tailwind'; +import { BrowseScreenProps } from '../navigation/tabs/BrowseStack'; + +const placeholderLocationData = [ + { + id: 1, + name: 'Spacedrive' + }, + { + id: 2, + name: 'Classified' + } +]; +const placeholderTagsData = [ + { + id: 1, + name: 'Secret', + color: tw.color('blue-500') + }, + { + id: 2, + name: 'OBS', + color: tw.color('purple-500') + }, + { + id: 3, + name: 'BlackMagic', + color: tw.color('red-500') + } +]; + +const BrowseScreen = ({ navigation }: BrowseScreenProps<'Browse'>) => { + return ( + + + {placeholderLocationData.map((location) => ( + navigation.navigate('Location', { id: location.id })} + /> + ))} + {/* Add Location */} + + + Add Location + + + + {/* Tags */} + + + {placeholderTagsData.map((tag) => ( + navigation.navigate('Tag', { id: tag.id })} + tagColor={tag.color as ColorValue} + /> + ))} + + + ); +}; + +export default BrowseScreen; diff --git a/apps/mobile/src/screens/Location.tsx b/apps/mobile/src/screens/Location.tsx new file mode 100644 index 000000000..7d54b8eff --- /dev/null +++ b/apps/mobile/src/screens/Location.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Text, View } from 'react-native'; + +import tw from '../lib/tailwind'; + +export default function LocationScreen({ navigation, route }: any) { + const { id } = route.params; + return ( + + Location {id} + + ); +} diff --git a/apps/mobile/src/screens/NotFound.tsx b/apps/mobile/src/screens/NotFound.tsx new file mode 100644 index 000000000..0f04aec50 --- /dev/null +++ b/apps/mobile/src/screens/NotFound.tsx @@ -0,0 +1,15 @@ +import { Text, TouchableOpacity, View } from 'react-native'; + +import tw from '../lib/tailwind'; +import type { RootStackScreenProps } from '../navigation'; + +export default function NotFoundScreen({ navigation }: RootStackScreenProps<'NotFound'>) { + return ( + + This screen doesn't exist. + navigation.replace('Root')} style={tw`mt-4 py-4`}> + Go to home screen! + + + ); +} diff --git a/apps/mobile/src/screens/Overview.tsx b/apps/mobile/src/screens/Overview.tsx new file mode 100644 index 000000000..eeb174d23 --- /dev/null +++ b/apps/mobile/src/screens/Overview.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { FlatList, Text, View } from 'react-native'; + +import { Button } from '../components/base/Button'; +import Device from '../components/device/Device'; +import DrawerScreenWrapper from '../components/drawer/DrawerScreenWrapper'; +import VirtualizedListWrapper from '../components/layout/VirtualizedListWrapper'; +import OverviewStats from '../containers/OverviewStats'; +import tw from '../lib/tailwind'; +import type { TabScreenProps } from '../navigation/TabNavigator'; + +const placeholderOverviewStats = { + id: 1, + total_bytes_capacity: '8093333345230', + preview_media_bytes: '2304387532', + library_db_size: '83345230', + total_file_count: 20342345, + total_bytes_free: '89734502034', + total_bytes_used: '8093333345230', + total_unique_bytes: '9347397', + date_captured: '2020-01-01' +}; + +const placeholderDevices: any = [ + { + name: "James' iPhone 12", + size: '47.9GB', + locations: [], + type: 'phone' + }, + { + name: "James' MacBook Pro", + size: '1TB', + locations: [], + type: 'laptop' + }, + { + name: "James' Toaster", + size: '1PB', + locations: [], + type: 'desktop' + }, + { + name: 'Spacedrive Server', + size: '5GB', + locations: [], + type: 'server' + } +]; + +export default function OverviewScreen({ navigation }: TabScreenProps<'Overview'>) { + return ( + + + + {/* Header */} + + {/* TODO: Header with a button to open drawer! */} + + + {/* Stats */} + + + {/* Devices */} + index.toString()} + renderItem={({ item }) => ( + + )} + /> + + + + ); +} diff --git a/apps/mobile/src/screens/Photos.tsx b/apps/mobile/src/screens/Photos.tsx new file mode 100644 index 000000000..ca3f4e7c6 --- /dev/null +++ b/apps/mobile/src/screens/Photos.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Text, View } from 'react-native'; + +import tw from '../lib/tailwind'; +import type { TabScreenProps } from '../navigation/TabNavigator'; + +export default function PhotosScreen({ navigation }: TabScreenProps<'Photos'>) { + return ( + + Photos + + ); +} diff --git a/apps/mobile/src/screens/Spaces.tsx b/apps/mobile/src/screens/Spaces.tsx new file mode 100644 index 000000000..4b3481acf --- /dev/null +++ b/apps/mobile/src/screens/Spaces.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +import tw from '../lib/tailwind'; +import type { TabScreenProps } from '../navigation/TabNavigator'; + +export default function SpacesScreen({ navigation }: TabScreenProps<'Spaces'>) { + return ( + + Spaces + + ); +} diff --git a/apps/mobile/src/screens/Tag.tsx b/apps/mobile/src/screens/Tag.tsx new file mode 100644 index 000000000..ea41fac20 --- /dev/null +++ b/apps/mobile/src/screens/Tag.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Text, View } from 'react-native'; + +import tw from '../lib/tailwind'; + +export default function TagScreen({ navigation, route }: any) { + const { id } = route.params; + return ( + + Tag {id} + + ); +} diff --git a/apps/mobile/src/screens/modals/settings/Settings.tsx b/apps/mobile/src/screens/modals/settings/Settings.tsx new file mode 100644 index 000000000..e6e747bb3 --- /dev/null +++ b/apps/mobile/src/screens/modals/settings/Settings.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Text, View } from 'react-native'; + +import tw from '../../../lib/tailwind'; +import { RootStackScreenProps } from '../../../navigation'; + +export default function SettingsScreen({ navigation }: RootStackScreenProps<'Settings'>) { + return ( + + Settings + + + ); +} diff --git a/apps/mobile/src/screens/onboarding/Onboarding.tsx b/apps/mobile/src/screens/onboarding/Onboarding.tsx new file mode 100644 index 000000000..f656bb835 --- /dev/null +++ b/apps/mobile/src/screens/onboarding/Onboarding.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Image, Text, View } from 'react-native'; + +import { FadeInUpAnimation, LogoAnimation } from '../../components/animation/layout'; +import { AnimatedButton } from '../../components/base/Button'; +import { setItemToStorage } from '../../lib/storage'; +import tw from '../../lib/tailwind'; +import type { OnboardingStackScreenProps } from '../../navigation/OnboardingNavigator'; +import { useOnboardingStore } from '../../stores/useOnboardingStore'; + +const OnboardingScreen = ({ navigation }: OnboardingStackScreenProps<'Onboarding'>) => { + const { hideOnboarding } = useOnboardingStore(); + + function onButtonPress() { + // Persist onboarding state only when app in production for now. + // if (process.env.NODE_ENV === 'production') { + // setItemToStorage('@onboarding', '1'); + // } + setItemToStorage('@onboarding', '1'); + // TODO: Add a loading indicator to button as this takes a second or so. + hideOnboarding(); + } + + return ( + + {/* Logo */} + + + {/* TODO: Change this to @sd/assets. */} + + + + {/* Text */} + + + + A file explorer from the future. + + + + + Combine your drives and clouds into one database that you can organize and explore from + any device. + + + + {/* Get Started Button */} + + + + Get Started + + + + + ); +}; + +export default OnboardingScreen; diff --git a/apps/mobile/src/stores/useOnboardingStore.ts b/apps/mobile/src/stores/useOnboardingStore.ts new file mode 100644 index 000000000..e76e5f1ce --- /dev/null +++ b/apps/mobile/src/stores/useOnboardingStore.ts @@ -0,0 +1,11 @@ +import create from 'zustand'; + +interface OnboardingState { + showOnboarding: boolean; + hideOnboarding: () => void; +} + +export const useOnboardingStore = create((set) => ({ + showOnboarding: true, + hideOnboarding: () => set((state) => ({ showOnboarding: false })) +})); diff --git a/apps/mobile/src/types/declarations.d.ts b/apps/mobile/src/types/declarations.d.ts new file mode 100644 index 000000000..9784ae9de --- /dev/null +++ b/apps/mobile/src/types/declarations.d.ts @@ -0,0 +1,13 @@ +declare module '*.svg' { + import React from 'react'; + import { SvgProps } from 'react-native-svg'; + const content: React.FC; + export default content; +} + +// This declaration is used by useNavigation, Link, ref etc. +declare global { + namespace ReactNavigation { + interface RootParamList extends RootStackParamList {} + } +} diff --git a/apps/mobile/src/types/helper.ts b/apps/mobile/src/types/helper.ts new file mode 100644 index 000000000..16136471f --- /dev/null +++ b/apps/mobile/src/types/helper.ts @@ -0,0 +1 @@ +export type valueof = T[keyof T]; diff --git a/apps/mobile/tailwind.config.js b/apps/mobile/tailwind.config.js new file mode 100644 index 000000000..4865f47ef --- /dev/null +++ b/apps/mobile/tailwind.config.js @@ -0,0 +1,63 @@ +module.exports = { + content: ['./screens/**/*.{js,ts,jsx}', './components/**/*.{js,ts,jsx}', 'App.tsx'], + theme: { + fontSize: { + 'tiny': '.65rem', + 'xs': '.75rem', + 'sm': '.84rem', + 'base': '1rem', + 'lg': '1.125rem', + 'xl': '1.25rem', + '2xl': '1.5rem', + '3xl': '1.875rem', + '4xl': '2.25rem', + '5xl': '3rem', + '6xl': '4rem', + '7xl': '5rem' + }, + extend: { + colors: { + primary: { + DEFAULT: '#2599FF', + 50: '#FFFFFF', + 100: '#F1F8FF', + 200: '#BEE1FF', + 300: '#8BC9FF', + 400: '#58B1FF', + 500: '#2599FF', + 600: '#0081F1', + 700: '#0065BE', + 800: '#004A8B', + 900: '#002F58' + }, + gray: { + DEFAULT: '#505468', + 50: '#F1F1F4', + 100: '#E8E9ED', + 150: '#E0E1E6', + 200: '#D8DAE3', + 250: '#D2D4DC', + 300: '#C0C2CE', + 350: '#A6AABF', + 400: '#9196A8', + 450: '#71758A', + 500: '#303544', + 550: '#20222d', + 600: '#171720', + 650: '#121219', + 700: '#121317', + 750: '#0D0E11', + 800: '#0C0C0F', + 850: '#08090D', + 900: '#060609', + 950: '#030303' + } + }, + extend: {} + } + }, + variants: { + extend: {} + }, + plugins: [] +}; diff --git a/apps/mobile/tsconfig.json b/apps/mobile/tsconfig.json new file mode 100644 index 000000000..3e08e07e7 --- /dev/null +++ b/apps/mobile/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": {} +} diff --git a/package.json b/package.json index c5b09150e..4ea589009 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "lint": "turbo run lint", "format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"", "desktop": "pnpm --filter @sd/desktop --", - "mobile": "pnpm --filter @sd/mobile -- ", "web": "pnpm --filter @sd/web -- ", "landing": "pnpm --filter @sd/landing -- ", "ui": "pnpm --filter @sd/ui -- ", @@ -38,7 +37,6 @@ "eslint-plugin-promise": ">=6.0.0 <7.0.0", "markdown-link-check": "^3.10.2", "prettier": "^2.7.1", - "turbo": "^1.3.4", "typescript": "^4.7.4" }, "overrides": { diff --git a/packages/interface/src/App.tsx b/packages/interface/src/App.tsx index 9d43224d1..02484c6df 100644 --- a/packages/interface/src/App.tsx +++ b/packages/interface/src/App.tsx @@ -36,7 +36,7 @@ function RouterContainer(props: { props: AppProps }) { ); } -export default function App(props: AppProps) { +export default function SpacedriveInterface(props: AppProps) { useInvalidateQuery(); return ( diff --git a/packages/interface/src/index.ts b/packages/interface/src/index.ts index 896dd051e..bd6e5d2ac 100644 --- a/packages/interface/src/index.ts +++ b/packages/interface/src/index.ts @@ -1,7 +1,7 @@ import { AppProps, Platform } from '@sd/client'; -import App from './App'; +import SpacedriveInterface from './App'; export type { AppProps, Platform }; -export default App; +export default SpacedriveInterface; diff --git a/packages/ui/package.json b/packages/ui/package.json index 813c90201..4bfd9245c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -40,6 +40,7 @@ "@storybook/preset-scss": "^1.0.3", "@storybook/react": "^6.5.9", "@storybook/testing-library": "^0.0.13", + "@tailwindcss/line-clamp": "^0.4.0", "@tailwindcss/typography": "^0.5.4", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed6ef7a0e16933d7afb4ebd8de1704e578bc1a1e..a06b10e41f893f0bd29b4f66cd1fe80be4f9ef29 100644 GIT binary patch delta 1183 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 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 184c1421e..4e23b291b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,5 @@ packages: - 'packages/*' - 'apps/*' - - 'core' \ No newline at end of file + - '!apps/mobile' + - 'core' diff --git a/tsconfig.json b/tsconfig.json index ae161f333..56ce6a098 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,9 @@ { "path": "apps/desktop" }, + { + "path": "apps/mobile" + }, { "path": "apps/web" }, From 56495719d51fa48c76c254513b70c3080c0bf293 Mon Sep 17 00:00:00 2001 From: Utku Bakir <74243531+utkubakir@users.noreply.github.com> Date: Wed, 10 Aug 2022 18:43:04 +0300 Subject: [PATCH 37/51] Move eslint to config package --- .eslintrc.js | 20 -------- apps/mobile/.eslintignore | 4 -- apps/mobile/.eslintrc.js | 8 ++++ apps/mobile/.eslintrc.json | 44 ------------------ apps/mobile/package.json | 4 +- apps/mobile/pnpm-lock.yaml | Bin 304815 -> 293354 bytes .../components/browse/BrowseLocationItem.tsx | 1 + package.json | 8 ---- packages/config/eslint-preset.js | 11 ----- packages/config/eslint-react-native.js | 42 +++++++++++++++++ packages/config/eslint-react.js | 44 ++++++++++++++++++ packages/config/package.json | 14 +++++- packages/interface/.eslintrc.js | 7 +++ packages/interface/package.json | 1 + pnpm-lock.yaml | Bin 668113 -> 663697 bytes 15 files changed, 116 insertions(+), 92 deletions(-) delete mode 100644 .eslintrc.js delete mode 100644 apps/mobile/.eslintignore create mode 100644 apps/mobile/.eslintrc.js delete mode 100755 apps/mobile/.eslintrc.json delete mode 100644 packages/config/eslint-preset.js create mode 100644 packages/config/eslint-react-native.js create mode 100644 packages/config/eslint-react.js create mode 100644 packages/interface/.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 16fa89674..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - project: [ - 'apps/desktop/tsconfig.json', - 'apps/web/tsconfig.json', - 'apps/landing/tsconfig.json', - 'apps/mobile/tsconfig.json', - 'packages/client/tsconfig.json', - 'packages/interface/tsconfig.json', - 'packages/ui/tsconfig.json' - ] - }, - plugins: ['@typescript-eslint'], - extends: ['standard-with-typescript', 'prettier'], - rules: { - '@typescript-eslint/explicit-function-return-type': 'off' - } -}; diff --git a/apps/mobile/.eslintignore b/apps/mobile/.eslintignore deleted file mode 100644 index b85fcb7dd..000000000 --- a/apps/mobile/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -android/ -ios/ -.expo \ No newline at end of file diff --git a/apps/mobile/.eslintrc.js b/apps/mobile/.eslintrc.js new file mode 100644 index 000000000..7eb0515e1 --- /dev/null +++ b/apps/mobile/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + ...require('@sd/config/eslint-react-native.js'), + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + }, + ignorePatterns: ['**/*.js', '**/*.json', 'android', 'ios', '.expo'] +}; diff --git a/apps/mobile/.eslintrc.json b/apps/mobile/.eslintrc.json deleted file mode 100755 index 3c3fc8145..000000000 --- a/apps/mobile/.eslintrc.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "env": { - "react-native/react-native": true - }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:react-hooks/recommended", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 12, - "sourceType": "module" - }, - "plugins": ["react", "react-native", "react-hooks", "@typescript-eslint"], - "rules": { - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "react/display-name": "off", - "react/prop-types": "off", - "no-control-regex": "off", - "no-mixed-spaces-and-tabs": ["warn", "smart-tabs"], - "react/react-in-jsx-scope": "off", - // Aggressively disable some rules for React Native. - "@typescript-eslint/strict-boolean-expressions": "off", - "@typescript-eslint/consistent-type-definitions": "off", - "@typescript-eslint/no-floating-promises": "off" - }, - "settings": { - "react": { - "version": "detect" // "detect" automatically picks the version you have installed. - } - } -} diff --git a/apps/mobile/package.json b/apps/mobile/package.json index bd510e6c2..cccdaf41b 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -41,15 +41,13 @@ "zustand": "^4.0.0" }, "devDependencies": { + "@sd/config": "file:../../packages/config", "@babel/core": "^7.12.9", "@babel/runtime": "^7.18.9", "@rnx-kit/metro-config": "^1.2.36", "@rnx-kit/metro-resolver-symlinks": "^0.1.21", "@types/react": "~18.0.15", "@types/react-native": "~0.69.5", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-native": "^4.0.0", "metro-minify-terser": "^0.72.0", "react-native-svg": "^12.4.3", "react-native-svg-transformer": "^1.0.0" diff --git a/apps/mobile/pnpm-lock.yaml b/apps/mobile/pnpm-lock.yaml index 6e200d20ad7957ded7182609a6a99adb603944b5..6fc817ce26cd17b977b095ebc2fb8f4a67f1a3ef 100644 GIT binary patch delta 363 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 RH?z~0oV$4V6LF8c{Qu&9Rtx|D diff --git a/apps/mobile/src/components/browse/BrowseLocationItem.tsx b/apps/mobile/src/components/browse/BrowseLocationItem.tsx index 8d0ba86d2..383b50624 100644 --- a/apps/mobile/src/components/browse/BrowseLocationItem.tsx +++ b/apps/mobile/src/components/browse/BrowseLocationItem.tsx @@ -11,6 +11,7 @@ interface BrowseLocationItemProps { const BrowseLocationItem: React.FC = (props) => { const { folderName, onPress } = props; + return ( diff --git a/package.json b/package.json index 4ea589009..763f3442a 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,7 @@ "@cspell/dict-typescript": "^2.0.1", "@evilmartians/lefthook": "^1.0.5", "@trivago/prettier-plugin-sort-imports": "^3.3.0", - "@typescript-eslint/eslint-plugin": "^5.30.7", - "@typescript-eslint/parser": "^5.30.7", "cspell": "^6.4.0", - "eslint": "^8.20.0", - "eslint-config-prettier": "^8.5.0", - "eslint-config-standard-with-typescript": "^22.0.0", - "eslint-plugin-import": ">=2.25.2 <3.0.0", - "eslint-plugin-n": ">=15.0.0 <16.0.0", - "eslint-plugin-promise": ">=6.0.0 <7.0.0", "markdown-link-check": "^3.10.2", "prettier": "^2.7.1", "typescript": "^4.7.4" diff --git a/packages/config/eslint-preset.js b/packages/config/eslint-preset.js deleted file mode 100644 index c99e0d465..000000000 --- a/packages/config/eslint-preset.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - extends: ['next', 'prettier'], - settings: { - next: { - rootDir: ['apps/*/', 'packages/*/'] - } - }, - rules: { - 'no-html-link-for-pages': 'off' - } -}; diff --git a/packages/config/eslint-react-native.js b/packages/config/eslint-react-native.js new file mode 100644 index 000000000..f275951ff --- /dev/null +++ b/packages/config/eslint-react-native.js @@ -0,0 +1,42 @@ +module.exports = { + env: { + 'react-native/react-native': true + }, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { + jsx: true + }, + ecmaVersion: 12, + sourceType: 'module' + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:@typescript-eslint/recommended' + ], + plugins: ['react', 'react-native'], + rules: { + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react/no-unescaped-entities': 'off', + 'react/react-in-jsx-scope': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'no-control-regex': 'off', + 'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs'] + }, + ignorePatterns: ['**/*.js', '**/*.json', 'node_modules'], + settings: { + react: { + version: 'detect' + } + } +}; diff --git a/packages/config/eslint-react.js b/packages/config/eslint-react.js new file mode 100644 index 000000000..5a232b963 --- /dev/null +++ b/packages/config/eslint-react.js @@ -0,0 +1,44 @@ +module.exports = { + env: { + browser: true, + node: true + }, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { + jsx: true + }, + ecmaVersion: 12, + sourceType: 'module' + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier' + ], + plugins: ['react'], + rules: { + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react/no-unescaped-entities': 'off', + 'react/react-in-jsx-scope': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'no-control-regex': 'off', + 'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs'] + }, + ignorePatterns: ['**/*.js', '**/*.json', 'node_modules'], + settings: { + react: { + version: 'detect' + } + } +}; diff --git a/packages/config/package.json b/packages/config/package.json index b91b1f836..79110a252 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -4,6 +4,16 @@ "main": "index.js", "license": "GPL-3.0-only", "files": [ - "eslint-preset.js" - ] + "eslint-react.js", + "eslint-react-native.js" + ], + "devDependencies": { + "eslint": "^8.21.0", + "@typescript-eslint/eslint-plugin": "^5.30.7", + "@typescript-eslint/parser": "^5.30.7", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-native": "^4.0.0" + } } diff --git a/packages/interface/.eslintrc.js b/packages/interface/.eslintrc.js new file mode 100644 index 000000000..b3e0f403d --- /dev/null +++ b/packages/interface/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + ...require('@sd/config/eslint-react.js'), + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +}; diff --git a/packages/interface/package.json b/packages/interface/package.json index 2f1961121..0d5a34959 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -67,6 +67,7 @@ "zustand": "4.0.0" }, "devDependencies": { + "@sd/config": "workspace:*", "@types/babel-core": "^6.25.7", "@types/byte-size": "^8.1.0", "@types/lodash": "^4.14.182", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a06b10e41f893f0bd29b4f66cd1fe80be4f9ef29..a3abc04873818f2420196cc625941f8ac3d32f66 100644 GIT binary patch delta 4007 zcmaJ^33OXmnLf`~Ptud^*l}#xalFVt;v|Z-uSsYkTl+3qizSK^?Yo}zw2LGY4$SE= zEltU5dl~3J1JlkF%23CgUY0=91BFnaNm_*v$XF@hc&I!qhYqjdk40eWrdBqoJ(Z(w|&5lQKXwv4Y= zhaG+NqoaqQ+rZ_*3ktA_en~Y-|4ueUAD`gU|1mbO{`!a$qdEL7w1h9EO{0AJLAiiF z#@|j~Zoi(XYsd z*9XQQA?W-x0S8v`5m@r#!!#Gu4kjx3OtHFGu&NQsr1S)9j{Z0{3TFd2XA_l(#5xkS zNG=gc;pPB7wz)gEkS57og^?&kDmWCt$47e8#Yi<%Pju%cbdfg)uf)LS&Qar&iB_tj zXlCUKRh`O|8@X(>k*&4rnMeel^5Q~455~a`2$6&*=>Gl+?J6=U!)E8OtygjNU>(}9 zvn!st4kCk4Bze!8x$E&NM3U_R+v7U=OOspRVE~AA@%w+b z>FO zT{f#srIO4lb|>SDZb>7mkH{)6mALGU#v=I=nWzU^L6uINw5aUKgabbOdG-vPK8l^= zr!v({sz_3ag(|sF$P_b$NHvy*o+p6NPc4@V4y!(HH~K5BrdMTjRP_=`G3l|{?8c}x zR~MESqppChY@q6S(qOULNo7>;v-&(vUnrGmmDCz*MW?Qbo$ib>>yw(iVn+?WIteDw zlSi?F3I3Mf*N>c!V3&q_v<$z1@zDOCVI2KUp2Vll>@(Wl*5DFckxc}^*(5?Vg2yd8vz^saX0wh2Jw%C;9k(h(i5+W z7#;s421*RxLVy8y;zod@pGd$9JQUgwF7;i-+Br#o0Av9C)*`qAtuBEhZ1{o&+_ORc zuU2rH*l^S#A9#N7|8xCFnb?6o9tEGoc-Pz*ezgo{;9X^)gdd56;rB?+#=%hkwdGP; zWSW9+D2xnGt_J~+1k~5LDArjzP9_&P`NpKE>1`hH_LbzI~RWo@eRmerM>veC2_vgW%;R|W7 z9sRrjE@1D5&6tV*UD(G;;3NirQvrkQy@F1DfO8mc9NH+byK~F|_`nAU7JNqEHw5=R zM{L?)k%R;T==T)(0p9b+N16cb=?r=3AMXK&*>LI(f`?xIGSK&-gZ~MBxUGA?Xyj2) zoJA*I1nV=%&1Kbbn8R89=)xdNPe9d2IeXB~an_YVv|~GKosT}ZpYT0HbFV^CRN|K7!s}z`s1#y&w3v3qJ#gGx#(-bR*80 z+K}7C?@n#q4Sv!XQ(LNSt*4omH5DnRdAVAbFP5!krNYo$DI}MK5ven)t435Ri_WQ0 z2Wt|q*rTITmSt_()?SgvS8Q&J)~t+Cd95dwb~-{@A$|ELQ+GOX0=i%8n}CAzIG0$J zcao;y$PxIJQ{3%rX26sX`lGkbcEwD^lU;*7x-s1@r1E4mlCQwI2uJAGMhxzx)M_YY zeA$>$S17a^nncF3QiwUju5u*YGP~63MyenY$Ad|qm~1XdmnlnGWAMo}B}da7qGF3G zf1)Auwf%w0Vj|WyYBccFXSw6>Y#irwS)1N?JVV>{$-YNh&QXXrspXJbCk>|J<%nrX zNQJ~Q4V6~s7ER7YZzZSDko9uTQ;4UOS%pogj~EMyl)KEt*D}@ z<%Y+%q-(;9FJp6EKHkUBB~^F%Vyb4BEM@np>OR%X6CV6`Fa?j-alydG!q=^7;~Opb zD}j3j5_tKO_-4?8jV!esFS&Du;f>J6dtoO5hqpgLYb=Nnt^&SxMF7R@zpoE z(@=5_aN(X0<2~<~UX^$4`umnn!=ax79G-cA30UqL(!+C`4xa|2@L&GG+0XMPf7kY?BX< z+5*iVOxFsLVk1q)$atb6{@e9?zQcrsiMX?Q{9Mn38Tij-oHrz38mkhO zU^vl=IuxEQ;00(o!y1A71lR?QJBY0?U&P1nysK|)B1J_?=|my1P$}gz)oOz3sa3$2 zBKQ>gbP<0FgQpK+JNzzhMe41JYaU~=fy5snquE5P zDvDGpOp~`)pkQJ{_dIJ1ed=*`h}Ww=ul<~@8bb3gu^$!mCjLCgsqI2X)tsk?dPV85 ziNiEG*U0{jmGjgGxILX>1$^}wYnLxSBjfJdsiI-fm*4j~_RN4-jS`mFT zn`(xm@qn5#8e(QoE!3pK}fLFGU_9XHk~m^raV*zUVfUXa+0Tj z9~~n(F9W5Au$UtJPju9 zIM4J6$BP^e%2zl!HhR9r8Q`Pyw{h;ncuX{kOAGl-Ap>=Yy9b^*$@v+o+|Jq2kKA{2 zwr)j@Z*X3@smmWcBZx3+$(QzI3tsCH_1Ka`hiQ`;v|4=78iLWIm7}*cz{TYWo zTF?cnNx#rxE;XxSi!u|msAC~jC}0TtJ#xol%Iy&fSM*Z1!CtePEqRTsxma*TTW*=H z`qvytN@ z_xGET>)*KNcf-F7F}=_HdG4(qc=H6&j~)^bkL~IC!krG{7rllS9SIUy480K|PAx= zl~m@BuIcXA@B7~Se($~Cd&eH#{@l;EAAOU3d1~LWH`lD7Y6Y69@0RV?%@a6#$Mg)F z+%z$pEHP<1Ggqs}nN+-*nrqSZ>|DK5rfSJ5UG9k!#5~T*WNQ!8#d4|2KiPJ}^~5}( zo+o5`$a#Do-!>%c{{{=pl?#mw&CD@J1lF39NRW4^#sz#&0bSkQ9tCUQ~*%85pk@K!u_Kfl{zvoMhx~iLcUg8(3 zRH~&$xzbP-E2%=JULdrEoFC&9AsbAY-E4BWet1h*f4uy%8ksBl{*?WkY)6FqA!ivY&viB*kVV@WevJa0QV1F8^m8{n#`oEhFX;L=VasvMuCE zbX=WqJ4TxticXNO8MKXVTc@n4@V1{wo7JX_f>@|%Z8#Cn<=u^hGp#X}${BO8XznD* zkhT^nyBA!#WYp8r*0jM+Rc>ZXu!W_u}Kot7CBZGnkZn z_(f!u8LS%7I0QEe#zDOo-FmxZgMgE4KutoJwxGlCfDBy$scH0R{}Z@RhK|At8T!S6 zk@w3`-rOa0j0?@6PBA=_7fQjq0~K>8x1-+^Kw+P#e=mWq9DVFVuid-Q`%rjW&wq*G z=pF>7&I}~QoL`B)H3UbmLxp0sg2(tg!TLTo8%p^XbEdF8no*VF2CX*P_GnZkk6i0) znOcman~!-@)v8IW%=wDGdLiplt2O1;LP?znbt}ynlgck8DC?rbLCf_)lL~jbt?H~B zkR@~zw|fuz6T!fsa~@R0&F)2M6hwApHT3S`D@OEAXnqD8@0$i8w`7vDn9!R<7i7e6 z!hwGIf{GFDW+!?=`XPI-38KGPafJi|$9^N3j-Z z?50d^<-Sj$Pa*>)+nx|i^p{{)O-{nQPY8xCs_YrEf?JbC_g~TEp2ly`=;D=xE}C-9 z#Y|eSHR^Pswp&Kd6MRno7^~L6SM%t9@X!-$xQ7eqx#dk?YB}(3#NZtUm0Zb}M>Yy( zxg90+*g(hdhcZ08b2GLM-Yla#;ofgxdtqKJSj`=*pufS`vBD5{poMa~;B^xsk~X9N@6={3UlR)FR|CF5{-R*1pbYlXuc zw^?}4q_{yB>NHaWosM1<0TP=2XIvaI5x^BO*nIcTee5a(zb;(%3WY~$I2UPCNu4$tE8Ya@rz4qgA@2cSO}Gip{9Yu6cg^B; zTxhrOH$oV@O(f+4dxdphKg77Fj|vy2dpSi42fr)igYl&B@7-|yTf!mk{XYn`I}n1C zZ^zPTFW98;{!VP1`?ehW)(mWZU$h-Q?ZO@gl_Y*6VF=F}B3c+%$7{0FzpEe5L%q zfr@z974X6D1U=mn;dz=DIFGvjH|JlB<%z-Cb5*KVDm1C;91jx;9;Joy5N=`c7EZeT zOD;wqqC1Q(>i3!#n&DjA6wcQQAxqZg4yJ5vrP|;0I-@>$A>k=hss*=$Xj%+4RoNU} zP^A@iHxW#ji+Od>i_>uGlyC};*D*1CWv_SyZhuKS0oSjSZh<#`E12oyU{|q}Y80rs zew|Se^V)gxK(o}PvSg*qkmYJ2o6)GNJpXTyx#j=^xK8J^n6w_UV2-);Z5xIAscPQi zO5v8AwIGi+Q_7q-?8v)<>IP}o%k3soRSbu{OvPJuE6EH^1@&d4$0PUEajiR{&;)sZ zus=0#gg5+{3=VryF}#;RC+*vIUe+dkocs6|=xSmx^J&54+Ce#+yIco&ZWGKw`36aU z(KYbGccdGqWU^0??jM-)73uURsylM0U>o1seZu!GDp8In^Xw@DImny;qlOFqWUqL= z`V&<4wzOM@&y(|-y>7XQqG}4&e6PzJD7{e!Gw+Lb zhHQ?-tS`vqq81`tsz%h#4y~qDsjk|v7;YOKDWAo(`qv=+&v4q9eB@!A9ZgKh(jdrGN4yZGEQzM$Og)>CEoh7QqhNG(THnUAip-qy? zd{#D(-YWi0#--hD-e>(WrlcLRV@&yFz zLBEeeZ9fRE-i^cc526-Ww@Ekww+1j-_9NcB9Pu9o%rde*oChF%8;bb?AJqdM9LcsE9x^Z?S}%v@WaB1;Vy)Et{@* zG=YH05agKil-HPJbvc*5I4YBD=~WjPbz|3Fn^@+b zyJbBhbZH?Q5Q-j);WNi>*`>kPry4N458I01cbAb(@a3*#1K4jt*1&BqN!PIjo1{-S z`_@h^$G~!hQ(Ri>2PSK6UaA`}6xGHwt*{V;m+`gDVLL&G^|oNsPzbq$-8xmI>liq^z^)mTJdCMp)%z?5|HU@`%#ifFi3r{V4U(d|96QNE~N ztZwjb2orI?4q`GCe*HSOGt|@FgByE5F%rBt&(iIP1#IcWeK;k z5rdbA6cOi?qgSR!NWbHOQ)eM-E6-+%}T zacmS(!gC>P6O2T76!sakGlIQ};BcghZURdb+XY`gB;Fa)IJJaZ7jnl{8ln|fELc6} zy29%u9U%=KFqciFFJe;KbG$<$k#c&GFzNkjxgn&@WhvaMvH2~PC0ADEY&7KVsJ6|> zH7U9r>esEK(}GEkC+jDX{`$d`if(7S>cE02t~6@`CU2E)dbOSC60K!AW{pABtZD2H zL!+Q?UJcosu_t+BmHRLaLO^yOR)iBNbQZ?;V>s{=nt8yU$FAYNzaP6Ff$u)R zlQ=V>RMt(}SgX_sIvP=RTOVNpxJ6$J^JikzI-&~^MWn2zd^wV_MzWP69WopB6++w5 zYD(6EE*H#@4v%B0{%~PjMD@E^@cv4? zi+d`A?a{$dZ_c!LCAyZ|dmP(1+=olh9q#7ycrM_q2kW>_ZZ%q6%}9xgF&?K`o(tA% z{-QCOFgUu-R76wIrWP}Pttr?@m|SWFWlniR#^$1lT8h`a%0xB2xSPI(9ojbNGk@|t_UaV8a+n{E9DNtVxHIoze>};(FBW~> z084lCGqZb~A`y4oA##b}p~v|l(^0Qz1@{M!=x0VKi-jV%@ea`__fL0;wkWtYkBK5f z{nn3bofTt)_?BF(74JdePJXyIIEX{L z#jkAPjvW;L70T_P#QA}#;u1sE=^_Ou)u^0%DldL<%^;YX&19suXmIMyRdvH(j>YA}bY3`MFmWh$!8 z2gzW-VCv){J}uq?$3x;ZaPD*bQc5t4U~A#M&+#+I&}YT7!(j09rx{XGb$7Jka+VzY zBq0*0RyzS}t`_kH!=5&w$fanVW>HR*{mpV#QE@HsbSaTEMN@WyOjl}&qAqABYz*${ zB+5Y*6|fdeOZFC2I${;q`l5L1ngOVeKa7sR>-UQ>G*5LP{_p&mJ^g_Ad$)u4Ej|S+ zCM6;`tB?$HU&AGTxB))6ogb*&SCRyWxy^?peB^n5j|t(SBa$)h3!jm^v#JkV?(m;U zp56%u?fkeR_=x0Bec*XZGQ_<-CVgfXeEJUF%Ezylz9xlp|HccPut{^s@La!qE691i zdLMD|W8QSzoDDP9z+#lBmGrC7=2 zv+B}KtDowc-04KY5-HMhZ_=ftqISw-$V5yYKz5;9*XL>_M!l`aW%+@rewhXI9|BD9 z2ZN7FL};SK1OHypdTymd`jxrC;OlHt8bn|=D_D28LpsXgZK-m2Fm0l4mHrqROq6Eq_=$IKNOSZbq$Av~o|OI&fm=>3-}9q%33v7>=`T=tFfJL$ zfcqT(;mglR|6ByAf0VAq=sKk!;nYp&4sOHC(jftN@+Ijb+d;I1j{kLS=x Date: Thu, 11 Aug 2022 12:22:58 +0300 Subject: [PATCH 38/51] Fix types - Mobile --- apps/mobile/.eslintrc.js | 44 +++++++++++++++++++++--- apps/mobile/package.json | 14 ++++++-- apps/mobile/pnpm-lock.yaml | Bin 293354 -> 322567 bytes apps/mobile/src/types/declarations.d.ts | 1 + packages/config/eslint-react-native.js | 42 ---------------------- packages/config/package.json | 6 ++-- pnpm-lock.yaml | Bin 663697 -> 663002 bytes 7 files changed, 53 insertions(+), 54 deletions(-) delete mode 100644 packages/config/eslint-react-native.js diff --git a/apps/mobile/.eslintrc.js b/apps/mobile/.eslintrc.js index 7eb0515e1..d440a4ae9 100644 --- a/apps/mobile/.eslintrc.js +++ b/apps/mobile/.eslintrc.js @@ -1,8 +1,42 @@ module.exports = { - ...require('@sd/config/eslint-react-native.js'), - parserOptions: { - tsconfigRootDir: __dirname, - project: './tsconfig.json' + env: { + 'react-native/react-native': true }, - ignorePatterns: ['**/*.js', '**/*.json', 'android', 'ios', '.expo'] + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { + jsx: true + }, + ecmaVersion: 12, + sourceType: 'module' + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:@typescript-eslint/recommended' + ], + plugins: ['react', 'react-native'], + rules: { + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react/no-unescaped-entities': 'off', + 'react/react-in-jsx-scope': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'no-control-regex': 'off', + 'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs'] + }, + ignorePatterns: ['**/*.js', '**/*.json', 'node_modules', 'android', 'ios', '.expo'], + settings: { + react: { + version: 'detect' + } + } }; diff --git a/apps/mobile/package.json b/apps/mobile/package.json index cccdaf41b..9fe4e2594 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "expo start --dev-client", "android": "expo run:android", - "ios": "expo run:ios" + "ios": "expo run:ios", + "lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit" }, "dependencies": { "@expo/vector-icons": "^13.0.0", @@ -41,16 +42,23 @@ "zustand": "^4.0.0" }, "devDependencies": { - "@sd/config": "file:../../packages/config", "@babel/core": "^7.12.9", "@babel/runtime": "^7.18.9", "@rnx-kit/metro-config": "^1.2.36", "@rnx-kit/metro-resolver-symlinks": "^0.1.21", "@types/react": "~18.0.15", "@types/react-native": "~0.69.5", + "@typescript-eslint/eslint-plugin": "^5.30.7", + "@typescript-eslint/parser": "^5.30.7", + "eslint": "^8.21.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-native": "^4.0.0", "metro-minify-terser": "^0.72.0", "react-native-svg": "^12.4.3", - "react-native-svg-transformer": "^1.0.0" + "react-native-svg-transformer": "^1.0.0", + "typescript": "^4.7.4" }, "private": true } diff --git a/apps/mobile/pnpm-lock.yaml b/apps/mobile/pnpm-lock.yaml index 6fc817ce26cd17b977b095ebc2fb8f4a67f1a3ef..1cf083f98ee040cefbbe37285f0b834a66c23d33 100644 GIT binary patch delta 19255 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 sjweW`UA17EgpoNWHsi2h$8|i(zV?ZHVV9=%Aw zYN5=2t=3|C>Qim#EL|x-1dsVNH(WW5tw8;QF07cdSvJo(^9Pv~#L%_=d;jsg_kNz| zz1KU_=Bu|dhRXq4Hb2Xp7;jMC1nT(!^5a(BU8(EX z`LE#*$%$_vTT5Z$lV%p?37trwK@FQrbs&-m<2oE3(AW=-rgsD8TXHx z7?KAB#rh#H6zQsURu|V+)pvN>UX6s-HLQ)+*`3c+wbj>$i+z#DYwVkg!gkeP>8Vm% zi`8glu&%^k5D1iPbX13272%>aTRWfXa(b&>r7w1QD@y9uN7rtpOZS9r?+oPZ&6XAl zJc7%pVMMN>$x)f$@#Atx;i;4IST?&8@~4M*=9)aHrIv<;#@@OuCpdLhs;P^c?wLV7 zzb5e3N^V?@FB$1ZDW>sXF3i!=aHT-%nnV-Z+$eQ4_bZrrg%3OR^mZ+d@N5X*5OlkA zp~UVwJhzlhuV6&O<=uFF3BCL&W^n=GP6Ivv?n3UFgV@WNpJ7yxZ4kA*mM3w=B8B8lVh&wAEj0fET9sA~ diff --git a/apps/mobile/src/types/declarations.d.ts b/apps/mobile/src/types/declarations.d.ts index 9784ae9de..793f67889 100644 --- a/apps/mobile/src/types/declarations.d.ts +++ b/apps/mobile/src/types/declarations.d.ts @@ -8,6 +8,7 @@ declare module '*.svg' { // This declaration is used by useNavigation, Link, ref etc. declare global { namespace ReactNavigation { + // eslint-disable-next-line @typescript-eslint/no-empty-interface interface RootParamList extends RootStackParamList {} } } diff --git a/packages/config/eslint-react-native.js b/packages/config/eslint-react-native.js deleted file mode 100644 index f275951ff..000000000 --- a/packages/config/eslint-react-native.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = { - env: { - 'react-native/react-native': true - }, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaFeatures: { - jsx: true - }, - ecmaVersion: 12, - sourceType: 'module' - }, - extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react-hooks/recommended', - 'plugin:@typescript-eslint/recommended' - ], - plugins: ['react', 'react-native'], - rules: { - 'react/display-name': 'off', - 'react/prop-types': 'off', - 'react/no-unescaped-entities': 'off', - 'react/react-in-jsx-scope': 'off', - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - 'no-control-regex': 'off', - 'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs'] - }, - ignorePatterns: ['**/*.js', '**/*.json', 'node_modules'], - settings: { - react: { - version: 'detect' - } - } -}; diff --git a/packages/config/package.json b/packages/config/package.json index 79110a252..6d9f656ba 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -4,8 +4,7 @@ "main": "index.js", "license": "GPL-3.0-only", "files": [ - "eslint-react.js", - "eslint-react-native.js" + "eslint-react.js" ], "devDependencies": { "eslint": "^8.21.0", @@ -13,7 +12,6 @@ "@typescript-eslint/parser": "^5.30.7", "eslint-config-prettier": "^8.5.0", "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-native": "^4.0.0" + "eslint-plugin-react-hooks": "^4.6.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3abc04873818f2420196cc625941f8ac3d32f66..27c6b5734acdb00edb6c0770155b2ed0825bd0ed 100644 GIT binary patch delta 121 zcmbPuP~+BajSV~HCNDFToPHsTS!gq_yclD1q(Xb70wWMJZI4u7o*Fs1hEs6*-}9Wt zlQ)#Iwrf{0Z`ZD3nRT_@G>8?5*?^cGh&i^K260-~!xRTjj+Erve&r%32ix?NTb#n% QA71BdX4+nKi&NeW0It?7-v9sr delta 467 zcmcb$TVvuujSV~HCQp143ENL$iHzQ!0vrBh4KR!_!j=6WyJ) ziz4-nEQ&JBvz^@ioPvt8O)T=u3bO-qQ_@YdOuV&S65VoB(_Q^ss(g!5e6sUP)4bh- z{Ib0x%#zZB%L7B49V;d`?o^z9&zF^hJxo+xgFPim^@K abBU90yW>^PDyHdedpU)-AGppb Date: Thu, 11 Aug 2022 12:35:27 +0300 Subject: [PATCH 39/51] Fix types - Interface --- packages/interface/package.json | 3 ++- packages/interface/src/App.tsx | 6 +++--- packages/interface/src/AppRouter.tsx | 2 +- packages/interface/src/components/file/FileList.tsx | 8 +++++--- packages/interface/src/components/file/FileThumb.tsx | 2 +- packages/interface/src/components/file/Inspector.tsx | 1 + packages/interface/src/components/file/Sidebar.tsx | 3 ++- .../src/components/jobs/RunningJobsWidget.tsx | 3 ++- packages/interface/src/components/layout/TopBar.tsx | 6 +++--- .../interface/src/components/primitive/Codeblock.tsx | 2 +- .../interface/src/components/primitive/Listbox.tsx | 2 +- packages/interface/src/screens/Content.tsx | 2 +- packages/interface/src/screens/Debug.tsx | 2 +- packages/interface/src/screens/Explorer.tsx | 10 +++++----- packages/interface/src/screens/Overview.tsx | 5 +++-- packages/interface/src/screens/Photos.tsx | 2 +- packages/interface/src/screens/Redirect.tsx | 1 + packages/interface/src/screens/Tag.tsx | 4 ++-- .../settings/library/LibraryGeneralSettings.tsx | 3 +++ .../src/screens/settings/library/TagsSettings.tsx | 4 +++- .../src/screens/settings/node/LibrariesSettings.tsx | 2 +- 21 files changed, 43 insertions(+), 30 deletions(-) diff --git a/packages/interface/package.json b/packages/interface/package.json index 0d5a34959..e3222e971 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -11,7 +11,8 @@ "./components/*": "./src/components/*" }, "scripts": { - "icons": "./scripts/generateSvgImports.mjs" + "icons": "./scripts/generateSvgImports.mjs", + "lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit" }, "dependencies": { "@apollo/client": "^3.6.9", diff --git a/packages/interface/src/App.tsx b/packages/interface/src/App.tsx index 02484c6df..12d27ea0d 100644 --- a/packages/interface/src/App.tsx +++ b/packages/interface/src/App.tsx @@ -21,10 +21,10 @@ function RouterContainer(props: { props: AppProps }) { const { data: client } = useBridgeQuery(['getNode']); useEffect(() => { - setAppProps({ + setAppProps((appProps) => ({ ...appProps, data_path: client?.data_path - }); + })); }, [client?.data_path]); return ( @@ -40,7 +40,7 @@ export default function SpacedriveInterface(props: AppProps) { useInvalidateQuery(); return ( - {}}> + {import.meta.env.MODE === 'development' && } diff --git a/packages/interface/src/AppRouter.tsx b/packages/interface/src/AppRouter.tsx index c74fc65cb..cd1ed725b 100644 --- a/packages/interface/src/AppRouter.tsx +++ b/packages/interface/src/AppRouter.tsx @@ -44,7 +44,7 @@ export function AppRouter() { if (libraryState.currentLibraryUuid === null && libraries && libraries.length > 0) { libraryState.switchLibrary(libraries[0].uuid); } - }, [libraryState.currentLibraryUuid, libraries]); + }, [libraryState, libraryState.currentLibraryUuid, libraries]); return ( <> diff --git a/packages/interface/src/components/file/FileList.tsx b/packages/interface/src/components/file/FileList.tsx index 4f88db3f5..a8f89d0db 100644 --- a/packages/interface/src/components/file/FileList.tsx +++ b/packages/interface/src/components/file/FileList.tsx @@ -73,10 +73,11 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb index: goingUp ? selectedRowIndex - 1 : selectedRowIndex }); } - }, [selectedRowIndex]); + }, [goingUp, selectedRowIndex]); useEffect(() => { setLocationId(props.location_id); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.location_id]); useKey('ArrowUp', (e) => { @@ -168,7 +169,7 @@ interface RenderItemProps { const RenderGridItem: React.FC = ({ item, index, dirId }) => { const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore(); - let [_, setSearchParams] = useSearchParams(); + const [_, setSearchParams] = useSearchParams(); return ( = ({ item, index, dirId }) => { const RenderRow: React.FC = ({ item, index, dirId }) => { const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore(); const isActive = selectedRowIndex === index; - let [_, setSearchParams] = useSearchParams(); + const [_, setSearchParams] = useSearchParams(); return useMemo( () => ( @@ -217,6 +218,7 @@ const RenderRow: React.FC = ({ item, index, dirId }) => { ))}

), + // eslint-disable-next-line react-hooks/exhaustive-deps [item.id, isActive] ); }; diff --git a/packages/interface/src/components/file/FileThumb.tsx b/packages/interface/src/components/file/FileThumb.tsx index 162712245..e82f46832 100644 --- a/packages/interface/src/components/file/FileThumb.tsx +++ b/packages/interface/src/components/file/FileThumb.tsx @@ -32,7 +32,7 @@ export default function FileThumb(props: { } if (icons[props.file.extension as keyof typeof icons]) { - let Icon = icons[props.file.extension as keyof typeof icons]; + const Icon = icons[props.file.extension as keyof typeof icons]; return ; } return
; diff --git a/packages/interface/src/components/file/Inspector.tsx b/packages/interface/src/components/file/Inspector.tsx index 8d08d26ea..427e87260 100644 --- a/packages/interface/src/components/file/Inspector.tsx +++ b/packages/interface/src/components/file/Inspector.tsx @@ -62,6 +62,7 @@ export const Inspector = (props: { return () => { clearTimeout(handler); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [note]); const toggleFavorite = () => { diff --git a/packages/interface/src/components/file/Sidebar.tsx b/packages/interface/src/components/file/Sidebar.tsx index 5012f89ba..a9e074ead 100644 --- a/packages/interface/src/components/file/Sidebar.tsx +++ b/packages/interface/src/components/file/Sidebar.tsx @@ -18,7 +18,7 @@ import RunningJobsWidget from '../jobs/RunningJobsWidget'; import { MacTrafficLights } from '../os/TrafficLights'; import { DefaultProps } from '../primitive/types'; -interface SidebarProps extends DefaultProps {} +type SidebarProps = DefaultProps; export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) => ( @@ -88,6 +88,7 @@ export const Sidebar: React.FC = (props) => { useEffect(() => { if (libraries && !currentLibraryUuid) initLibraries(libraries); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [libraries, currentLibraryUuid]); const { mutate: createLocation } = useLibraryMutation('locations.create'); diff --git a/packages/interface/src/components/jobs/RunningJobsWidget.tsx b/packages/interface/src/components/jobs/RunningJobsWidget.tsx index d042f4a15..5c23d5821 100644 --- a/packages/interface/src/components/jobs/RunningJobsWidget.tsx +++ b/packages/interface/src/components/jobs/RunningJobsWidget.tsx @@ -55,8 +55,9 @@ export default function RunningJobsWidget() { return (
- {jobs?.map((job) => ( + {jobs?.map((job, index) => ( , HTMLButtonElement> { icon: React.ComponentType; @@ -18,7 +18,7 @@ export interface TopBarButtonProps left?: boolean; right?: boolean; } -interface SearchBarProps extends DefaultProps {} +type SearchBarProps = DefaultProps; const TopBarButton: React.FC = ({ icon: Icon, @@ -90,7 +90,7 @@ export const TopBar: React.FC = (props) => { } }); - let navigate = useNavigate(); + const navigate = useNavigate(); return ( <>
diff --git a/packages/interface/src/screens/Content.tsx b/packages/interface/src/screens/Content.tsx index 3166734ff..cb94ffc1e 100644 --- a/packages/interface/src/screens/Content.tsx +++ b/packages/interface/src/screens/Content.tsx @@ -1,6 +1,6 @@ import React from 'react'; -export const ContentScreen: React.FC<{}> = (props) => { +export const ContentScreen: React.FC = (props) => { // const [address, setAddress] = React.useState(''); return (
diff --git a/packages/interface/src/screens/Debug.tsx b/packages/interface/src/screens/Debug.tsx index 804b2066a..97326b21a 100644 --- a/packages/interface/src/screens/Debug.tsx +++ b/packages/interface/src/screens/Debug.tsx @@ -4,7 +4,7 @@ import React, { useContext } from 'react'; import CodeBlock from '../components/primitive/Codeblock'; -export const DebugScreen: React.FC<{}> = (props) => { +export const DebugScreen: React.FC = (props) => { const appPropsContext = useContext(AppPropsContext); const { data: nodeState } = useBridgeQuery(['getNode']); const { data: libraryState } = useBridgeQuery(['library.get']); diff --git a/packages/interface/src/screens/Explorer.tsx b/packages/interface/src/screens/Explorer.tsx index d8ca6e066..8c408fb4f 100644 --- a/packages/interface/src/screens/Explorer.tsx +++ b/packages/interface/src/screens/Explorer.tsx @@ -6,12 +6,12 @@ import { FileList } from '../components/file/FileList'; import { Inspector } from '../components/file/Inspector'; import { TopBar } from '../components/layout/TopBar'; -export const ExplorerScreen: React.FC<{}> = () => { - let [searchParams] = useSearchParams(); - let path = searchParams.get('path') || ''; +export const ExplorerScreen: React.FC = () => { + const [searchParams] = useSearchParams(); + const path = searchParams.get('path') || ''; - let { id } = useParams(); - let location_id = Number(id); + const { id } = useParams(); + const location_id = Number(id); const [limit, setLimit] = React.useState(100); diff --git a/packages/interface/src/screens/Overview.tsx b/packages/interface/src/screens/Overview.tsx index 60ca38d26..2011fbff5 100644 --- a/packages/interface/src/screens/Overview.tsx +++ b/packages/interface/src/screens/Overview.tsx @@ -70,7 +70,7 @@ const StatItem: React.FC = (props) => { setCount((count) => count + 1); }, quadratic(appProps?.demoMode ? 1000 : 500, +size.value, count)); } - }, [count, size]); + }, [appProps?.demoMode, count, size]); return (
{ setOverviewStats(newStatistics); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [appProps, libraryStatistics]); // useEffect(() => { @@ -187,7 +188,7 @@ export const OverviewScreen = () => { {}} + // ctaAction={() => {}} ctaLabel="Connect" trigger={ } From 07aeb3171286a89c3d9a01446fcb5dc8ce2bac1a Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Fri, 12 Aug 2022 14:48:49 +0800 Subject: [PATCH 40/51] fix Windows support --- apps/desktop/src/index.tsx | 2 +- packages/client/src/context/AppPropsContext.tsx | 2 +- packages/interface/src/App.tsx | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/index.tsx b/apps/desktop/src/index.tsx index 002eb506e..d1f54a364 100644 --- a/apps/desktop/src/index.tsx +++ b/apps/desktop/src/index.tsx @@ -29,7 +29,7 @@ function App() { } } - const [platform, setPlatform] = useState('macOS'); + const [platform, setPlatform] = useState('unknown'); const [focused, setFocused] = useState(true); useEffect(() => { diff --git a/packages/client/src/context/AppPropsContext.tsx b/packages/client/src/context/AppPropsContext.tsx index 59e6cf4b7..9b9f4d4a2 100644 --- a/packages/client/src/context/AppPropsContext.tsx +++ b/packages/client/src/context/AppPropsContext.tsx @@ -2,7 +2,7 @@ import { createContext } from 'react'; export const AppPropsContext = createContext(null); -export type Platform = 'browser' | 'macOS' | 'windows' | 'linux'; +export type Platform = 'browser' | 'macOS' | 'windows' | 'linux' | 'unknown'; export type CdnUrl = 'internal' | string; export interface AppProps { diff --git a/packages/interface/src/App.tsx b/packages/interface/src/App.tsx index 02484c6df..6954d9969 100644 --- a/packages/interface/src/App.tsx +++ b/packages/interface/src/App.tsx @@ -6,7 +6,7 @@ import { useBridgeQuery, useInvalidateQuery } from '@sd/client'; -import { QueryClientProvider } from '@tanstack/react-query'; +import { QueryClientProvider, defaultContext } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import React, { useEffect, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; @@ -41,8 +41,11 @@ export default function SpacedriveInterface(props: AppProps) { return ( {}}> - - {import.meta.env.MODE === 'development' && } + + {/* The `context={defaultContext}` part is required for this to work on Windows. Why, idk, don't question it */} + {import.meta.env.MODE === 'development' && ( + + )} From 810bf3556510fa096b91bc4f6453df5e2b8b2a23 Mon Sep 17 00:00:00 2001 From: ned-park <87483870+ned-park@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:20:22 -0400 Subject: [PATCH 41/51] Fixes Tauri dependencies for Debian Buster (stable), fixes warning for users without cargo or pnpm installed --- .github/scripts/setup-system.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/scripts/setup-system.sh b/.github/scripts/setup-system.sh index 83c90ae1b..e12cfa8d1 100755 --- a/.github/scripts/setup-system.sh +++ b/.github/scripts/setup-system.sh @@ -1,7 +1,5 @@ #!/bin/bash -set -e - echo "Setting up your system for Spacedrive development!" which cargo &> /dev/null @@ -29,7 +27,7 @@ if [[ "$OSTYPE" == "linux-gnu"* ]]; then else DEBIAN_FFMPEG_DEPS="libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavresample-dev libavutil-dev libswscale-dev libswresample-dev ffmpeg" # FFMPEG dependencies fi - DEBIAN_TAURI_DEPS="libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libappindicator3-dev librsvg2-dev" # Tauri dependencies + DEBIAN_TAURI_DEPS="libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev" # Tauri dependencies DEBIAN_BINDGEN_DEPS="pkg-config clang" # Bindgen dependencies - it's used by a dependency of Spacedrive sudo apt-get -y update From 3a35e7fdb4f9f83722ee38aafaeab8e0a21d22ff Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 19 Aug 2022 07:24:15 +0800 Subject: [PATCH 42/51] update pnpm-lock --- pnpm-lock.yaml | Bin 668113 -> 667748 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a06b10e41f893f0bd29b4f66cd1fe80be4f9ef29..62449eeb5a58de825fd11c5355b6b7140d30d273 100644 GIT binary patch delta 893 zcmYjOX-HI280F4=+kJ0z#+mVr=#*pSxHK-KIhu-&Wt*fn5ELOf2_;g2KU@k_1c{15 z!&}`5+BKq$RPbvf|5TJwHbtvNg0?^_7_{3mSHgeiaPD```Mw(*O&l9ZJa<1F<2wWs!tNngVoTwvB35BkeJDA`grEfpnac>Y)QxdtaTo)IbMqm2BApMPYa6 z;BflhYoqTnOtdvhrzawfeP1nQh|0Q^rt2QM$fnc&Oeby8<7rTL(0Vn7p7g48r#Fmt z*z#ynWHo#2B05biu+p&@oiaU-e#|tt8SP-SBXuU5$Wc@ln~M%|bUhax2_dM>LuUo% z^PxEA@Sz#7#)ooeu;-7E2HjQYP!)Tt8#E}phK9|2)cgjikpB?L#asQ+2JlDY7J(}X zPHbYYr{i5yXh$sW_)jOp%~@E62Toifvop=I%0fIX5uZ1(rS(W-?G;#JUy||FDe|q^ z_&5%A;qvO#zaHDta28@a2{y8h3M|6ybX*-i+45gTaUrh3?9x2E2UgF=O%i*v1b0u- z50v0uG9{#PIj)$+7rwR$gZm#;VM{j-NX)T^sPN$wE;fMgEWQ~NvH{B=PA`CEiqIgk z^`fA`-7sOX9bECkdIulbUnJD|!9j)cSUytME8HmK+5#jVa`Qe5Q$!2=P7%A7Ku@D+ z+5mS(#gS=n8A;xi{i~#v608eIG02q^DThLYJY)8Atx3@Pn?wj+vYFyJJ2+&pe?DS6Hh2~8`=cs?}mCQlIW zR=Jm4$m2^k1c@0u_sA7F#OYxr8Z1vqKVqjJi7Et#$+yrSt8o7X0SWfKB0rPiXoPyv Q%A2l@S5HH%OYPPE0FqWcEC2ui delta 1111 zcmYk4eN0tl9LM*Z=bY!9=UiS6_pK|)%jCxEjkg5kML=q7a7{3jY;(hr%~(mkA-3ki zWU)~J@)W;{Q7j2_yqe1&u6AQho2;BTk(^Oo)5da(mTlM{C7pXuTmAR_?f3k?pYQW~ zo;$&`tGCmpu0@H#f=m%ijuN)$W>~XZiV}0)D6!&oLF|BJ6K_TL!BmQ-iJrnZk(0vC z)*9O-82XaCMIhNJDmF$6SCRefK1V4Le;SeEFQ+d0*T#zC0*`ptRU(G%jbh%lM-+NI za&Fe6SrD^Za*OBV^2E|cr_y}jaB-3d7;e$xjt~K7p5U%Z;g8Wpulr%K*%fzu#Em0v zlAa8!exk$8A~I;HSBuFIOl~7K%qk&cGW=amlHf);NyhjJQoK$K_UYKvL_TPOy+5iP zx-St89T&;G0d=)R$9va_@i=s(5l5#QE1?=oUy`9`8@0h333L!@>rL4DL{o*aiy_rm zLJjOnqBSa1^2NU}l$JmePE(S)cnX7Kh9O@dSWfHV6NDPA6Y6N~W zPA|rVmdg8QsZ8?W_e?n*XJ@HY7Q)-1j>|G$Q{@)LGW18v)eiyNA=_bJs;odDS+?QF zUiqoDFm+zmQQssV+zO|jQ|)-HPxdEfPzR*=Toc1YpeC82l zxH^nNTbp8g0Y(NC2gco2zKe(1`zGbes-ln$aO;ejE1fX90~$J*19P8d^$gY=mh6za zi$%g$Pm&b;tdV_Q*+0shc=SVdw8&!1i|j#8n1~UPYHK>or%*F|p{ih4fqFF>W>yrA z&sM2zd0{^c9#MNduu#qI5bRbpeEEd>ZHnn?2jjfTv2j}csS{?}%#}v_GzPDiYc||j zp*<*rxh;%CZ@Z>r%PZPM4Ak^$PF#0FTaK}uohP-gGoW2EdB0rNy0=;@J(kF?_+X}r z>&k)k1=%^U->+m~*JeJ>;fqf=$4w3VkJA4=+6KX|6ZmbI>*#r#PnU%LE{Ci+Zs6TH z9#F&ghBt5V2n^id7YIC&Yvu~q@7(0SQn-9j`nMkVfsZmMyvrR@^C2*LnGctjxGq8d kIa5w9aghcMCDe{phJMZgMuIuWy!seSCFl-BuRd=32NWfR4*&oF From dc81c83825d187106f28d8bda5e06aca126e6eea Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 19 Aug 2022 07:41:08 +0800 Subject: [PATCH 43/51] use latest prisma-client-rust --- Cargo.lock | Bin 165043 -> 170602 bytes core/Cargo.toml | 16 +++++++++++++--- core/prisma/Cargo.toml | 5 ++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c143a58ec412a659813abb47ca2d51b9943e242e..d4285c8fed94c934c71b81c163f37088f0f960a1 100644 GIT binary patch delta 6406 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 Wjcg7Y-2N@Wwp`95yQ42yx9LAu50=6J diff --git a/core/Cargo.toml b/core/Cargo.toml index 3eddc564e..2399c855c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,7 +8,8 @@ repository = "https://github.com/spacedriveapp/spacedrive" edition = "2021" [features] -p2p = [] # This feature controlls whether the Spacedrive Core contains the Peer to Peer syncing engine (It isn't required for the hosted core so we can disable it). +p2p = [ +] # This feature controlls whether the Spacedrive Core contains the Peer to Peer syncing engine (It isn't required for the hosted core so we can disable it). [dependencies] hostname = "0.3.1" @@ -26,8 +27,17 @@ rmp = "^0.8.11" rmp-serde = "^1.1.0" # Project dependencies -prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", branch = "0.6.0", features = ["rspc"] } -rspc = { version = "0.0.4", features = ["axum", "tauri", "uuid", "chrono", "tracing"] } +prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", rev = "6a0119bce951c8d956542a59b2f783fc5a591fc7", features = [ + "rspc", + "sqlite-create-many", +] } +rspc = { version = "0.0.4", features = [ + "axum", + "tauri", + "uuid", + "chrono", + "tracing", +] } walkdir = "^2.3.2" uuid = { version = "1.1.2", features = ["v4", "serde"] } sysinfo = "0.23.9" diff --git a/core/prisma/Cargo.toml b/core/prisma/Cargo.toml index d9b60cd5a..b24c05939 100644 --- a/core/prisma/Cargo.toml +++ b/core/prisma/Cargo.toml @@ -4,4 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", branch = "0.6.0", features = ["rspc"] } +prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", rev = "6a0119bce951c8d956542a59b2f783fc5a591fc7", features = [ + "rspc", + "sqlite-create-many", +] } From f7efb72a231c4d37327cfd1b496276e629b89442 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 19 Aug 2022 08:58:27 +0800 Subject: [PATCH 44/51] update to latest prisma-client-rust --- Cargo.lock | Bin 165043 -> 170602 bytes Cargo.toml | 11 ++++++----- core/Cargo.toml | 18 ++++++++++++++---- core/prisma/Cargo.toml | 5 ++++- core/src/api/files.rs | 12 ++++++------ core/src/api/locations.rs | 12 ++++++------ core/src/api/tags.rs | 14 +++++--------- core/src/file/cas/identifier.rs | 26 ++++++++++++++------------ core/src/file/indexer.rs | 3 ++- core/src/job/job_manager.rs | 26 +++++++++++++++----------- core/src/job/mod.rs | 4 ++-- core/src/library/library_manager.rs | 8 ++------ core/src/sys/locations.rs | 4 ++-- core/src/sys/volumes.rs | 7 ++----- core/src/util/db.rs | 13 ++++++++----- 15 files changed, 88 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c143a58ec412a659813abb47ca2d51b9943e242e..d4285c8fed94c934c71b81c163f37088f0f960a1 100644 GIT binary patch delta 6406 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 Wjcg7Y-2N@Wwp`95yQ42yx9LAu50=6J diff --git a/Cargo.toml b/Cargo.toml index 5b06d38b2..bb526593c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [workspace] members = [ - "apps/desktop/src-tauri", - "core", - "core/prisma", - "core/derive", - "apps/server" + "apps/desktop/src-tauri", + "core", + "core/prisma", + "core/derive", + "apps/server", ] +resolver = "2" diff --git a/core/Cargo.toml b/core/Cargo.toml index 3eddc564e..f199eefdd 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,7 +8,8 @@ repository = "https://github.com/spacedriveapp/spacedrive" edition = "2021" [features] -p2p = [] # This feature controlls whether the Spacedrive Core contains the Peer to Peer syncing engine (It isn't required for the hosted core so we can disable it). +p2p = [ +] # This feature controlls whether the Spacedrive Core contains the Peer to Peer syncing engine (It isn't required for the hosted core so we can disable it). [dependencies] hostname = "0.3.1" @@ -26,8 +27,17 @@ rmp = "^0.8.11" rmp-serde = "^1.1.0" # Project dependencies -prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", branch = "0.6.0", features = ["rspc"] } -rspc = { version = "0.0.4", features = ["axum", "tauri", "uuid", "chrono", "tracing"] } +prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", rev = "6a0119bce951c8d956542a59b2f783fc5a591fc7", features = [ + "rspc", + "sqlite-create-many", +] } +rspc = { version = "0.0.4", features = [ + "axum", + "tauri", + "uuid", + "chrono", + "tracing", +] } walkdir = "^2.3.2" uuid = { version = "1.1.2", features = ["v4", "serde"] } sysinfo = "0.23.9" @@ -42,7 +52,7 @@ webp = "0.2.2" ffmpeg-next = "5.0.3" fs_extra = "1.2.0" tracing = "0.1.35" -tracing-subscriber = "0.3.14" +tracing-subscriber = { version = "0.3.14", features = ["env-filter"] } async-stream = "0.3.3" once_cell = "1.13.0" ctor = "0.1.22" diff --git a/core/prisma/Cargo.toml b/core/prisma/Cargo.toml index d9b60cd5a..b24c05939 100644 --- a/core/prisma/Cargo.toml +++ b/core/prisma/Cargo.toml @@ -4,4 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", branch = "0.6.0", features = ["rspc"] } +prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", rev = "6a0119bce951c8d956542a59b2f783fc5a591fc7", features = [ + "rspc", + "sqlite-create-many", +] } diff --git a/core/src/api/files.rs b/core/src/api/files.rs index bb33d9acd..e8329c6ac 100644 --- a/core/src/api/files.rs +++ b/core/src/api/files.rs @@ -26,8 +26,7 @@ pub(crate) fn mount() -> RouterBuilder { library .db .file() - .find_unique(file::id::equals(args.id)) - .update(vec![file::note::set(args.note)]) + .update(file::id::equals(args.id), vec![file::note::set(args.note)]) .exec() .await?; @@ -54,8 +53,10 @@ pub(crate) fn mount() -> RouterBuilder { library .db .file() - .find_unique(file::id::equals(args.id)) - .update(vec![file::favorite::set(args.favorite)]) + .update( + file::id::equals(args.id), + vec![file::favorite::set(args.favorite)], + ) .exec() .await?; @@ -82,8 +83,7 @@ pub(crate) fn mount() -> RouterBuilder { library .db .file() - .find_unique(file::id::equals(id)) - .delete() + .delete(file::id::equals(id)) .exec() .await?; diff --git a/core/src/api/locations.rs b/core/src/api/locations.rs index 666afcc48..d690597f5 100644 --- a/core/src/api/locations.rs +++ b/core/src/api/locations.rs @@ -133,8 +133,10 @@ pub(crate) fn mount() -> RouterBuilder { library .db .location() - .find_unique(location::id::equals(args.id)) - .update(vec![location::name::set(args.name)]) + .update( + location::id::equals(args.id), + vec![location::name::set(args.name)], + ) .exec() .await?; @@ -147,16 +149,14 @@ pub(crate) fn mount() -> RouterBuilder { library .db .file_path() - .find_many(vec![file_path::location_id::equals(Some(location_id))]) - .delete() + .delete_many(vec![file_path::location_id::equals(Some(location_id))]) .exec() .await?; library .db .location() - .find_unique(location::id::equals(location_id)) - .delete() + .delete(location::id::equals(location_id)) .exec() .await?; diff --git a/core/src/api/tags.rs b/core/src/api/tags.rs index c8a75bc9d..b7a7996d5 100644 --- a/core/src/api/tags.rs +++ b/core/src/api/tags.rs @@ -97,8 +97,10 @@ pub(crate) fn mount() -> RouterBuilder { library .db .tag() - .find_unique(tag::id::equals(args.id)) - .update(vec![tag::name::set(args.name), tag::color::set(args.color)]) + .update( + tag::id::equals(args.id), + vec![tag::name::set(args.name), tag::color::set(args.color)], + ) .exec() .await?; @@ -117,13 +119,7 @@ pub(crate) fn mount() -> RouterBuilder { .mutation("delete", |ctx, arg: LibraryArgs| async move { let (id, library) = arg.get_library(&ctx).await?; - library - .db - .tag() - .find_unique(tag::id::equals(id)) - .delete() - .exec() - .await?; + library.db.tag().delete(tag::id::equals(id)).exec().await?; invalidate_query!( library, diff --git a/core/src/file/cas/identifier.rs b/core/src/file/cas/identifier.rs index 8cbe5bbcc..ac13c7b92 100644 --- a/core/src/file/cas/identifier.rs +++ b/core/src/file/cas/identifier.rs @@ -3,7 +3,7 @@ use super::checksum::generate_cas_id; use crate::{ job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, library::LibraryContext, - prisma::{self, file, file_path, location}, + prisma::{file, file_path, location}, }; use chrono::{DateTime, FixedOffset}; use prisma_client_rust::{prisma_models::PrismaValue, raw, raw::Raw, Direction}; @@ -160,10 +160,10 @@ impl StatefulJob for FileIdentifierJob { if let Err(e) = library_ctx .db .file_path() - .find_unique(file_path::id::equals( - *cas_lookup.get(&existing_file.cas_id).unwrap(), - )) - .update(vec![file_path::file_id::set(Some(existing_file.id))]) + .update( + file_path::id::equals(*cas_lookup.get(&existing_file.cas_id).unwrap()), + vec![file_path::file_id::set(Some(existing_file.id))], + ) .exec() .await { @@ -205,6 +205,7 @@ impl StatefulJob for FileIdentifierJob { ), values, )) + .exec() .await .unwrap_or_else(|e| { error!("Error inserting files: {:#?}", e); @@ -219,10 +220,10 @@ impl StatefulJob for FileIdentifierJob { .library_ctx() .db .file_path() - .find_unique(file_path::id::equals( - *cas_lookup.get(&created_file.cas_id).unwrap(), - )) - .update(vec![file_path::file_id::set(Some(created_file.id))]) + .update( + file_path::id::equals(*cas_lookup.get(&created_file.cas_id).unwrap()), + vec![file_path::file_id::set(Some(created_file.id))], + ) .exec() .await { @@ -277,12 +278,13 @@ struct CountRes { pub async fn count_orphan_file_paths( ctx: &LibraryContext, location_id: i64, -) -> Result { +) -> Result { let files_count = ctx.db ._query_raw::(raw!( "SELECT COUNT(*) AS count FROM file_paths WHERE file_id IS NULL AND is_dir IS FALSE AND location_id = {}", PrismaValue::Int(location_id) )) + .exec() .await?; Ok(files_count[0].count.unwrap_or(0)) } @@ -290,7 +292,7 @@ pub async fn count_orphan_file_paths( pub async fn get_orphan_file_paths( ctx: &LibraryContext, cursor: i32, -) -> Result, prisma::QueryError> { +) -> Result, prisma_client_rust::QueryError> { info!( "discovering {} orphan file paths at cursor: {:?}", CHUNK_SIZE, cursor @@ -302,7 +304,7 @@ pub async fn get_orphan_file_paths( file_path::is_dir::equals(false), ]) .order_by(file_path::id::order(Direction::Asc)) - .cursor(file_path::id::cursor(cursor)) + .cursor(file_path::id::equals(cursor)) .take(CHUNK_SIZE as i64) .exec() .await diff --git a/core/src/file/indexer.rs b/core/src/file/indexer.rs index b9189b17d..36c75b470 100644 --- a/core/src/file/indexer.rs +++ b/core/src/file/indexer.rs @@ -86,6 +86,7 @@ impl StatefulJob for IndexerJob { .library_ctx() .db ._query_raw::(raw!("SELECT MAX(id) id FROM file_paths")) + .exec() .await { Ok(rows) => rows[0].id.unwrap_or(0), @@ -241,7 +242,7 @@ impl StatefulJob for IndexerJob { files ); - let count = ctx.library_ctx().db._execute_raw(raw).await; + let count = ctx.library_ctx().db._execute_raw(raw).exec().await; info!("Inserted {:?} records", count); diff --git a/core/src/job/job_manager.rs b/core/src/job/job_manager.rs index 140b1ddf2..f79996579 100644 --- a/core/src/job/job_manager.rs +++ b/core/src/job/job_manager.rs @@ -6,7 +6,7 @@ use crate::{ }, job::{worker::Worker, DynJob, Job, JobError}, library::LibraryContext, - prisma::{self, job, node}, + prisma::{job, node}, }; use int_enum::IntEnum; use rspc::Type; @@ -117,7 +117,9 @@ impl JobManager { ret } - pub async fn get_history(ctx: &LibraryContext) -> Result, prisma::QueryError> { + pub async fn get_history( + ctx: &LibraryContext, + ) -> Result, prisma_client_rust::QueryError> { let jobs = ctx .db .job() @@ -291,15 +293,17 @@ impl JobReport { pub async fn update(&self, ctx: &LibraryContext) -> Result<(), JobError> { ctx.db .job() - .find_unique(job::id::equals(self.id.as_bytes().to_vec())) - .update(vec![ - job::status::set(self.status.int_value()), - job::data::set(self.data.clone()), - job::task_count::set(self.task_count), - job::completed_task_count::set(self.completed_task_count), - job::date_modified::set(chrono::Utc::now().into()), - job::seconds_elapsed::set(self.seconds_elapsed), - ]) + .update( + job::id::equals(self.id.as_bytes().to_vec()), + vec![ + job::status::set(self.status.int_value()), + job::data::set(self.data.clone()), + job::task_count::set(self.task_count), + job::completed_task_count::set(self.completed_task_count), + job::date_modified::set(chrono::Utc::now().into()), + job::seconds_elapsed::set(self.seconds_elapsed), + ], + ) .exec() .await?; Ok(()) diff --git a/core/src/job/mod.rs b/core/src/job/mod.rs index 31b7ce16a..3ecd9015e 100644 --- a/core/src/job/mod.rs +++ b/core/src/job/mod.rs @@ -1,4 +1,4 @@ -use crate::{prisma, sys::LocationError}; +use crate::sys::LocationError; use rmp_serde::{decode::Error as DecodeError, encode::Error as EncodeError}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{collections::VecDeque, fmt::Debug}; @@ -14,7 +14,7 @@ pub use worker::*; #[derive(Error, Debug)] pub enum JobError { #[error("Database error: {0}")] - DatabaseError(#[from] prisma::QueryError), + DatabaseError(#[from] prisma_client_rust::QueryError), #[error("Location error: {0}")] LocationError(#[from] LocationError), #[error("I/O error: {0}")] diff --git a/core/src/library/library_manager.rs b/core/src/library/library_manager.rs index 9f40cc5d6..2b873ea7a 100644 --- a/core/src/library/library_manager.rs +++ b/core/src/library/library_manager.rs @@ -10,11 +10,7 @@ use tokio::sync::RwLock; use uuid::Uuid; use crate::{ - invalidate_query, - node::Platform, - prisma::{self, node}, - util::db::load_and_migrate, - NodeContext, + invalidate_query, node::Platform, prisma::node, util::db::load_and_migrate, NodeContext, }; use super::{LibraryConfig, LibraryConfigWrapped, LibraryContext}; @@ -36,7 +32,7 @@ pub enum LibraryManagerError { #[error("error serializing or deserializing the JSON in the config file")] Json(#[from] serde_json::Error), #[error("Database error: {0}")] - Database(#[from] prisma::QueryError), + Database(#[from] prisma_client_rust::QueryError), #[error("Library not found error")] LibraryNotFound, #[error("error migrating the config file")] diff --git a/core/src/sys/locations.rs b/core/src/sys/locations.rs index 6c1b54a99..1a34e1066 100644 --- a/core/src/sys/locations.rs +++ b/core/src/sys/locations.rs @@ -8,7 +8,7 @@ use crate::{ invalidate_query, job::Job, library::LibraryContext, - prisma::{self, location}, + prisma::location, }; use rspc::ErrorCode; @@ -195,7 +195,7 @@ pub enum LocationError { #[error("Failed to connect to database (error: {0:?})")] IOError(io::Error), #[error("Database error")] - DatabaseError(#[from] prisma::QueryError), + DatabaseError(#[from] prisma_client_rust::QueryError), } impl From for rspc::Error { diff --git a/core/src/sys/volumes.rs b/core/src/sys/volumes.rs index 81a3c3e06..dbf581570 100644 --- a/core/src/sys/volumes.rs +++ b/core/src/sys/volumes.rs @@ -1,7 +1,4 @@ -use crate::{ - library::LibraryContext, - prisma::{self, volume::*}, -}; +use crate::{library::LibraryContext, prisma::volume::*}; use rspc::Type; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -24,7 +21,7 @@ pub struct Volume { #[derive(Error, Debug)] pub enum VolumeError { #[error("Database error: {0}")] - DatabaseErr(#[from] prisma::QueryError), + DatabaseErr(#[from] prisma_client_rust::QueryError), #[error("FromUtf8Error: {0}")] FromUtf8Error(#[from] std::string::FromUtf8Error), } diff --git a/core/src/util/db.rs b/core/src/util/db.rs index 4ced4361a..9df21142e 100644 --- a/core/src/util/db.rs +++ b/core/src/util/db.rs @@ -14,7 +14,7 @@ pub enum MigrationError { #[error("An error occurred while initialising a new database connection")] DatabaseInitialization(#[from] NewClientError), #[error("An error occurred with the database while applying migrations")] - DatabaseError(#[from] prisma_client_rust::queries::Error), + DatabaseError(#[from] prisma_client_rust::QueryError), #[error("An error occurred reading the embedded migration files. {0}. Please report to Spacedrive developers!")] InvalidEmbeddedMigration(&'static str), } @@ -27,11 +27,12 @@ pub async fn load_and_migrate(db_url: &str) -> Result(raw!( "SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'" )) + .exec() .await? .is_empty(); if migrations_table_missing { - client._execute_raw(raw!(INIT_MIGRATION)).await?; + client._execute_raw(raw!(INIT_MIGRATION)).exec().await?; } let mut migration_directories = MIGRATIONS_DIR @@ -102,11 +103,13 @@ pub async fn load_and_migrate(db_url: &str) -> Result>(); let steps = &steps[0..steps.len() - 1]; for (i, step) in steps.iter().enumerate() { - client._execute_raw(raw!(*step)).await?; + client._execute_raw(raw!(*step)).exec().await?; client .migration() - .find_unique(migration::checksum::equals(checksum.clone())) - .update(vec![migration::steps_applied::set(i as i32 + 1)]) + .update( + migration::checksum::equals(checksum.clone()), + vec![migration::steps_applied::set(i as i32 + 1)], + ) .exec() .await?; } From 3372e02a79e255a875ae5ce6bf70eda498833195 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 19 Aug 2022 09:03:47 +0800 Subject: [PATCH 45/51] keep dist folder in desktop --- .gitignore | 4 ++-- apps/desktop/dist/.gitignore | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 apps/desktop/dist/.gitignore diff --git a/.gitignore b/.gitignore index 53da2a22b..500449750 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules .next dist +!apps/desktop/dist *.tsbuildinfo package-lock.json .eslintcache @@ -15,7 +16,6 @@ storybook-static/ cache .env vendor/ -dist data node_modules packages/turbo-server/data/ @@ -61,4 +61,4 @@ todos.md examples/*/*.lock /target -/sdserver_data \ No newline at end of file +/sdserver_data diff --git a/apps/desktop/dist/.gitignore b/apps/desktop/dist/.gitignore new file mode 100644 index 000000000..c53272268 --- /dev/null +++ b/apps/desktop/dist/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore +# This is done so that Tauri never complains that '../dist does not exist' From 96e26c8d81734f7be59c887face39aab1109dfb2 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 19 Aug 2022 09:57:52 +0800 Subject: [PATCH 46/51] fix clippy ci --- .github/workflows/clippy.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index f2c039e33..3de8c04df 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -48,10 +48,6 @@ jobs: working-directory: core if: steps.cache-prisma.outputs.cache-hit != 'true' run: cargo run -p prisma-cli --release -- generate - - # This is do the proc-macro `tauri::generate_context!()` doesn't panic - - name: Create fake `dist` folder - run: mkdir ./apps/desktop/dist - name: Run Clippy uses: actions-rs/clippy-check@v1 From dbea174a12b917d65d4afff121049105220fe23d Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sat, 20 Aug 2022 23:55:29 -0700 Subject: [PATCH 47/51] hotfix: (macOS) app platform prop not being set correctly --- packages/interface/src/App.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/interface/src/App.tsx b/packages/interface/src/App.tsx index 81765f019..9b522df65 100644 --- a/packages/interface/src/App.tsx +++ b/packages/interface/src/App.tsx @@ -39,6 +39,12 @@ function RouterContainer(props: { props: AppProps }) { export default function SpacedriveInterface(props: AppProps) { useInvalidateQuery(); + // hotfix for bug where props are not updated, not sure of the cause + if (props.platform === 'unknown') { + // this should be a loading screen if we can't fix the issue above + return <>; + } + return ( From 5112185c7590f3bea942266f8b8323f4faa49e58 Mon Sep 17 00:00:00 2001 From: ned-park <87483870+ned-park@users.noreply.github.com> Date: Tue, 23 Aug 2022 12:03:56 -0400 Subject: [PATCH 48/51] Adds trap for error handling from set -e, updates error handling conditionals for set -e compatibility, updates debian tauri dependencies --- .github/scripts/setup-system.sh | 52 ++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/.github/scripts/setup-system.sh b/.github/scripts/setup-system.sh index e12cfa8d1..433f73112 100755 --- a/.github/scripts/setup-system.sh +++ b/.github/scripts/setup-system.sh @@ -1,16 +1,24 @@ #!/bin/bash +set -e + +script_failure() { + echo "An error occurred while performing the task on line $1" >&2 + echo "Setup for Spacedrive development failed" >&2 +} + +trap 'script_failure $LINENO' ERR + echo "Setting up your system for Spacedrive development!" -which cargo &> /dev/null -if [ $? -eq 1 ]; then +if ! which cargo &> /dev/null; then echo "Rust was not detected on your system. Ensure the 'rustc' and 'cargo' binaries are in your \$PATH." exit 1 fi if [ "${SPACEDRIVE_SKIP_PNPM_CHECK:-}" != "true" ]; then - which pnpm &> /dev/null - if [ $? -eq 1 ]; then + + if ! which pnpm &> /dev/null; then echo "PNPM was not detected on your system. Ensure the 'pnpm' command is in your \$PATH. You are not able to use Yarn or NPM." exit 1 fi @@ -18,6 +26,42 @@ else echo "Skipped PNPM check!" fi +if [ "$1" == "mobile" ]; then + echo "Setting up for mobile development!" + + # IOS targets + if [[ "$OSTYPE" == "darwin"* ]]; then + echo "Installing IOS Rust targets..." + + if ! /usr/bin/xcodebuild -version; then + echo "Xcode is not installed! Ensure you have it installed!" + exit 1 + fi + + rustup target add aarch64-apple-ios + fi + + # Android requires python + if ! command -v python3 &> /dev/null + then + echo "Python3 could not be found. This is required for Android mobile development!" + exit 1 + fi + + # Android targets + echo "Installing Android Rust targets..." + rustup target add armv7-linux-androideabi # for arm + rustup target add i686-linux-android # for x86 + rustup target add aarch64-linux-android # for arm64 + rustup target add x86_64-linux-android # for x86_64 + rustup target add x86_64-unknown-linux-gnu # for linux-x86-64 + rustup target add x86_64-apple-darwin # for darwin x86_64 (if you have an Intel MacOS) + rustup target add aarch64-apple-darwin # for darwin arm64 (if you have a M1 MacOS) + rustup target add x86_64-pc-windows-gnu # for win32-x86-64-gnu + rustup target add x86_64-pc-windows-msvc # for win32-x86-64-msvc +fi + + if [[ "$OSTYPE" == "linux-gnu"* ]]; then if which apt-get &> /dev/null; then echo "Detected 'apt' based distro!" From 8cc0967baead812c489edc3166fc9ceb0d6549c8 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 29 Aug 2022 17:36:34 +0800 Subject: [PATCH 49/51] fix landing page --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 763f3442a..2c1dbe5bb 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,8 @@ "private": true, "scripts": { "prep": "pnpm db:gen", - "build": "turbo run build", - "landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web", "db:migrate": "pnpm core prisma migrate dev", "db:gen": "pnpm core prisma generate", - "lint": "turbo run lint", "format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"", "desktop": "pnpm --filter @sd/desktop --", "web": "pnpm --filter @sd/web -- ", From b01887c5ab9cef1f150a0488a906a5e6f7f3f53b Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 29 Aug 2022 19:59:09 +0800 Subject: [PATCH 50/51] Rustify mobile (#361) * Refactor navigation flow & types * Remove drawer screen wrapper * Remove DrawerItem + cleanup * Switch to JS Stack Nav & header animations * [WIP] Spacedrive core on Android & IOS * Update Podfile and cleanup Contributing guide. * Remove @sd/core from mobile * File Modal * Prettify File Modal & Add date-fns * IOS subscriptions * Update package versions * Custom header for stack screens * android subscriptions * Animate Drawer button & template for Search screen * Search header * Fix Search icon being weird * Merge branch 'main' into rustify-mobile * fix rspc dep + setup script for mobile * Less margin on header * Move shared assets & drawer logo for mobile * support for IOS simulator * add type safe rspc hooks to mobile * Cleanup PR & Update packages * Updated bindings from main * Update lefthook.yml * Remove `tag` folder from core The `tag` folder came back from the dead. Maybe it got confused in merge conflict? * update pnpm lockfile + fix tsc errors * fix asset import Co-authored-by: Utku Bakir <74243531+utkubakir@users.noreply.github.com> --- CONTRIBUTING.md | 38 +- Cargo.lock | Bin 170602 -> 171012 bytes Cargo.toml | 18 +- apps/desktop/src-tauri/Cargo.toml | 1 + apps/desktop/src-tauri/src/main.rs | 2 +- apps/mobile/README.md | 2 + apps/mobile/android/app/build.gradle | 18 + .../com/spacedrive/app/MainApplication.java | 2 +- .../main/java/com/spacedrive/app/SDCore.java | 76 +++ .../com/spacedrive/app/SpacedrivePackage.java | 28 + apps/mobile/android/build.gradle | 4 + apps/mobile/ios/Podfile.lock | 510 +++++++++--------- .../ios/Spacedrive.xcodeproj/project.pbxproj | 244 +++++++-- apps/mobile/ios/Spacedrive/Info.plist | 152 +++--- apps/mobile/ios/Spacedrive/SDCore.h | 17 + apps/mobile/ios/Spacedrive/SDCore.m | 81 +++ apps/mobile/metro.config.js | 3 - apps/mobile/package.json | 50 +- apps/mobile/pnpm-lock.yaml | Bin 322567 -> 326993 bytes apps/mobile/rust/Cargo.toml | 27 + apps/mobile/rust/src/android.rs | 111 ++++ apps/mobile/rust/src/ios.rs | 96 ++++ apps/mobile/rust/src/lib.rs | 31 ++ apps/mobile/src/App.tsx | 39 +- apps/mobile/src/assets/temp/folder-white.svg | 15 - apps/mobile/src/assets/temp/folder.svg | 16 - apps/mobile/src/components/base/Divider.tsx | 14 + .../components/browse/BrowseLocationItem.tsx | 2 +- apps/mobile/src/components/device/Device.tsx | 21 +- .../src/components/drawer/DrawerContent.tsx | 118 ++-- .../src/components/drawer/DrawerItem.tsx | 34 -- .../components/drawer/DrawerLocationItem.tsx | 26 + .../src/components/drawer/DrawerLogo.tsx | 19 + .../components/drawer/DrawerScreenWrapper.tsx | 31 -- .../src/components/drawer/DrawerTagItem.tsx | 26 + apps/mobile/src/components/file/FileIcon.tsx | 73 +++ apps/mobile/src/components/file/FileItem.tsx | 80 +-- apps/mobile/src/components/header/Header.tsx | 41 ++ .../icons/{Folder.tsx => FolderIcon.tsx} | 5 +- .../src/components/modals/FileDetails.tsx | 1 - .../src/components/modals/FileModal.tsx | 128 +++++ .../src/components/modals/GlobalModals.tsx | 13 + .../modals/layout/ModalBackdrop.tsx | 10 + .../components/modals/layout/ModalHandle.tsx | 16 + apps/mobile/src/containers/OverviewStats.tsx | 13 +- apps/mobile/src/hooks/rspc.ts | 152 ++++++ .../mobile/src/navigation/DrawerNavigator.tsx | 16 +- .../src/navigation/OnboardingNavigator.tsx | 6 +- apps/mobile/src/navigation/SharedScreens.tsx | 36 ++ apps/mobile/src/navigation/TabNavigator.tsx | 41 +- apps/mobile/src/navigation/index.tsx | 33 +- .../src/navigation/tabs/BrowseStack.tsx | 35 +- .../src/navigation/tabs/OverviewStack.tsx | 35 ++ .../src/navigation/tabs/PhotosStack.tsx | 35 ++ .../src/navigation/tabs/SpacesStack.tsx | 35 ++ apps/mobile/src/screens/Browse.tsx | 4 +- apps/mobile/src/screens/Location.tsx | 3 +- apps/mobile/src/screens/Overview.tsx | 48 +- apps/mobile/src/screens/Photos.tsx | 4 +- apps/mobile/src/screens/Spaces.tsx | 11 +- apps/mobile/src/screens/Tag.tsx | 3 +- apps/mobile/src/screens/modals/Search.tsx | 57 ++ .../src/screens/modals/settings/Settings.tsx | 2 +- .../src/screens/onboarding/Onboarding.tsx | 7 +- apps/mobile/src/stores/useLibraryStore.ts | 13 + apps/mobile/src/stores/useModalStore.ts | 19 + apps/mobile/src/types/bindings.ts | 117 ++++ apps/mobile/src/types/declarations.d.ts | 8 - apps/server/Cargo.toml | 1 + core/Cargo.toml | 12 +- core/derive/Cargo.toml | 11 - core/derive/src/lib.rs | 42 -- core/index.ts | 135 +++-- core/src/api/mod.rs | 6 +- core/src/api/utils/invalidate.rs | 8 +- core/src/encode/metadata.rs | 8 +- core/src/lib.rs | 4 +- core/src/tag/mod.rs | 185 ------- lefthook.yml | 4 +- package.json | 4 +- .../temp => packages/assets/images}/logo.png | Bin packages/assets/svgs/folder-white.svg | 15 + packages/assets/svgs/folder.svg | 16 + packages/client/package.json | 2 +- .../interface/src/assets/svg/folder-white.svg | 15 - packages/interface/src/assets/svg/folder.svg | 16 - .../src/components/file/FileItem.tsx | 2 +- .../interface/src/components/icons/Folder.tsx | 5 +- pnpm-lock.yaml | Bin 663002 -> 663100 bytes 89 files changed, 2365 insertions(+), 1098 deletions(-) create mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/SDCore.java create mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/SpacedrivePackage.java create mode 100644 apps/mobile/ios/Spacedrive/SDCore.h create mode 100644 apps/mobile/ios/Spacedrive/SDCore.m create mode 100644 apps/mobile/rust/Cargo.toml create mode 100644 apps/mobile/rust/src/android.rs create mode 100644 apps/mobile/rust/src/ios.rs create mode 100644 apps/mobile/rust/src/lib.rs delete mode 100644 apps/mobile/src/assets/temp/folder-white.svg delete mode 100644 apps/mobile/src/assets/temp/folder.svg create mode 100644 apps/mobile/src/components/base/Divider.tsx delete mode 100644 apps/mobile/src/components/drawer/DrawerItem.tsx create mode 100644 apps/mobile/src/components/drawer/DrawerLocationItem.tsx create mode 100644 apps/mobile/src/components/drawer/DrawerLogo.tsx delete mode 100644 apps/mobile/src/components/drawer/DrawerScreenWrapper.tsx create mode 100644 apps/mobile/src/components/drawer/DrawerTagItem.tsx create mode 100644 apps/mobile/src/components/file/FileIcon.tsx create mode 100644 apps/mobile/src/components/header/Header.tsx rename apps/mobile/src/components/icons/{Folder.tsx => FolderIcon.tsx} (81%) delete mode 100644 apps/mobile/src/components/modals/FileDetails.tsx create mode 100644 apps/mobile/src/components/modals/FileModal.tsx create mode 100644 apps/mobile/src/components/modals/GlobalModals.tsx create mode 100644 apps/mobile/src/components/modals/layout/ModalBackdrop.tsx create mode 100644 apps/mobile/src/components/modals/layout/ModalHandle.tsx create mode 100644 apps/mobile/src/hooks/rspc.ts create mode 100644 apps/mobile/src/navigation/SharedScreens.tsx create mode 100644 apps/mobile/src/navigation/tabs/OverviewStack.tsx create mode 100644 apps/mobile/src/navigation/tabs/PhotosStack.tsx create mode 100644 apps/mobile/src/navigation/tabs/SpacesStack.tsx create mode 100644 apps/mobile/src/screens/modals/Search.tsx create mode 100644 apps/mobile/src/stores/useLibraryStore.ts create mode 100644 apps/mobile/src/stores/useModalStore.ts create mode 100644 apps/mobile/src/types/bindings.ts delete mode 100644 core/derive/Cargo.toml delete mode 100644 core/derive/src/lib.rs delete mode 100644 core/src/tag/mod.rs rename {apps/mobile/src/assets/temp => packages/assets/images}/logo.png (100%) create mode 100644 packages/assets/svgs/folder-white.svg create mode 100644 packages/assets/svgs/folder.svg delete mode 100644 packages/interface/src/assets/svg/folder-white.svg delete mode 100644 packages/interface/src/assets/svg/folder.svg diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee1475ee3..26e2000ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,39 +37,43 @@ This project uses [Cargo](https://doc.rust-lang.org/cargo/getting-started/instal > Note: MacOS M1 users should choose the customize option in the rustup init script and enter `x86_64-apple-darwin` as the default host triple instead of the default `aarch64-apple-darwin` -- `$ git clone https://github.com/spacedriveapp/spacedrive` -- `$ cd spacedrive` +- `git clone https://github.com/spacedriveapp/spacedrive` +- `cd spacedrive` - For Linux or MacOS users run: `./.github/scripts/setup-system.sh` - This will install FFMPEG and any other required dependencies for Spacedrive to build. - For Windows users run using PowerShell: `.\.github\scripts\setup-system.ps1` - This will install pnpm, LLVM, FFMPEG and any other required dependencies for Spacedrive to build. - Ensure you run it like documented above as it expects it is executed from the root of the repository. -- `$ pnpm i` -- `$ pnpm prep` - Runs all necessary codegen & builds required dependencies. +- `pnpm i` +- `pnpm prep` - Runs all necessary codegen & builds required dependencies. To quickly run only the desktop app after `prep` you can use: -- `$ pnpm desktop dev` +- `pnpm desktop dev` To run the landing page -- `$ pnpm web dev` - runs the web app for the embed -- `$ pnpm landing dev` - -To run mobile app - -- `$ cd apps/mobile && pnpm i` - As this is a seperated workspace, you need to do this! -- `$ pnpm android` - runs on Android Emulator -- `$ pnpm ios` - runs on iOS Emulator -- `$ pnpm dev` - For already bundled app - -You also need `expo-cli` installed globally. +- `pnpm web dev` - runs the web app for the embed +- `pnpm landing dev` If you are having issues ensure you are using the following versions of Rust and Node: -- Rust version: **1.62.0** +- Rust version: **1.63.0** - Node version: **17** +##### Mobile app + +To run mobile app + +- Install [Android Studio](https://developer.android.com/studio) for Android and [Xcode](https://apps.apple.com/au/app/xcode/id497799835) for IOS development +- `./.github/scripts/setup-system.sh mobile` + - The should setup most of the dependencies for the mobile app to build. +- You must also ensure [you must have NDK 24.0.8215888 and CMake](https://developer.android.com/studio/projects/install-ndk#default-version) in Android Studio +- `cd apps/mobile && pnpm i` - This is a separate workspace, you need to do this! +- `pnpm android` - runs on Android Emulator +- `pnpm ios` - runs on iOS Emulator +- `pnpm dev` - For already bundled app - This is only temporarily supported. The final app will require the Spacedrive Rust code which isn't included in Expo Go. + ### Pull Request When you're finished with the changes, create a pull request, also known as a PR. diff --git a/Cargo.lock b/Cargo.lock index d4285c8fed94c934c71b81c163f37088f0f960a1..a619404082d49fa89e469938199d73f556783820 100644 GIT binary patch delta 7052 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 f00}&B delta 6912 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 O?8(;YD{r2BT>LMnycqxh diff --git a/Cargo.toml b/Cargo.toml index bb526593c..507f92891 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,13 @@ [workspace] -members = [ - "apps/desktop/src-tauri", - "core", - "core/prisma", - "core/derive", - "apps/server", -] resolver = "2" +members = [ + "apps/desktop/src-tauri", + "apps/mobile/rust", + "core", + "core/prisma", + "apps/server" +] + +[patch.crates-io] +# We use this patch so we can compile for the IOS simulator on M1 +openssl-sys = { git = "https://github.com/spacedriveapp/rust-openssl" } \ No newline at end of file diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index edf6c13bb..9bbcfb57c 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -11,6 +11,7 @@ build = "build.rs" [dependencies] tauri = { version = "1.0.4", features = ["api-all", "macos-private-api"] } +rspc = { version = "0.0.4", features = ["tauri"] } sdcore = { path = "../../../core" } tokio = { version = "1.17.0", features = ["sync"] } window-shadows = "0.1.2" diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index 5916fddf5..8ae8ba8e3 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -23,7 +23,7 @@ async fn main() { let (node, router) = Node::new(data_dir).await; let app = tauri::Builder::default() - .plugin(sdcore::rspc::integrations::tauri::plugin(router, { + .plugin(rspc::integrations::tauri::plugin(router, { let node = node.clone(); move || node.get_request_context() })) diff --git a/apps/mobile/README.md b/apps/mobile/README.md index 105fc3296..8a79c0202 100644 --- a/apps/mobile/README.md +++ b/apps/mobile/README.md @@ -1 +1,3 @@ Make sure to run `pnpm i` in this folder after making changes to the `packages`. + +- Note: If you add/remove something from `packages/assets` folder, you need to delete node_modules and run `pnpm i` again to link it. diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index ff86ce652..d825f46f4 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -3,6 +3,24 @@ apply plugin: "com.android.application" import com.android.build.OutputFile import org.apache.tools.ant.taskdefs.condition.Os +apply plugin: 'org.mozilla.rust-android-gradle.rust-android' + +cargo { + module = "../../rust" + libname = "sdcore" + // profile = 'release', + pythonCommand = 'python3' + targets = ["arm", "arm64", "x86", "x86_64"] + targetDirectory = "../.././../../target" // Monorepo moment + +} + +tasks.whenTaskAdded { task -> + if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) { + task.dependsOn 'cargoBuild' + } +} + /** * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets * and bundleReleaseJsAndAssets). diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java index 8838de57a..1d47d6150 100644 --- a/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java @@ -34,7 +34,7 @@ public class MainApplication extends Application implements ReactApplication { @SuppressWarnings("UnnecessaryLocalVariable") List packages = new PackageList(this).getPackages(); // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); + packages.add(new com.spacedrive.app.SpacedrivePackage()); return packages; } diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/SDCore.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/SDCore.java new file mode 100644 index 000000000..782f44a1b --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/SDCore.java @@ -0,0 +1,76 @@ +package com.spacedrive.app; + +import android.content.Context; +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +import javax.annotation.Nullable; + +public class SDCore extends ReactContextBaseJavaModule { + SDCore(ReactApplicationContext context) { super(context); } + + private boolean registeredWithRust = false; + private int listeners = 0; + + @Override + public String getName() + { + return "SDCore"; + } + + static { + System.loadLibrary("sdcore"); + } + + // is exposed by Rust and is used to register the subscription + private native void registerCoreEventListener(); + + private native void handleCoreMsg(String query, Promise promise); + + @ReactMethod + public void sd_core_msg(String query, Promise promise) + { + this.handleCoreMsg(query, promise); + } + + public String getDataDirectory() + { + return getCurrentActivity().getFilesDir().toString(); + } + + @ReactMethod + public void addListener(String eventName) + { + if (!registeredWithRust) + { + this.registerCoreEventListener(); + } + + this.listeners++; + } + + @ReactMethod + public void removeListeners(Integer count) + { + this.listeners--; + } + + public void sendCoreEvent(String body) + { + if (this.listeners > 0) + { + this.getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("SDCoreEvent", body); + } + } +} \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/SpacedrivePackage.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/SpacedrivePackage.java new file mode 100644 index 000000000..b9fe3bc95 --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/SpacedrivePackage.java @@ -0,0 +1,28 @@ +package com.spacedrive.app; +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SpacedrivePackage implements ReactPackage { + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createNativeModules( + ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new SDCore(reactContext)); + + return modules; + } + +} \ No newline at end of file diff --git a/apps/mobile/android/build.gradle b/apps/mobile/android/build.gradle index a19a7b074..ab32c2ff7 100644 --- a/apps/mobile/android/build.gradle +++ b/apps/mobile/android/build.gradle @@ -24,11 +24,15 @@ buildscript { repositories { google() mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } } dependencies { classpath('com.android.tools.build:gradle:7.1.1') classpath('com.facebook.react:react-native-gradle-plugin') classpath('de.undercouch:gradle-download-task:5.0.1') + classpath('org.mozilla.rust-android-gradle:plugin:0.9.3') // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 804fee0e9..00b80fd0f 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -9,27 +9,27 @@ PODS: - ExpoModulesCore - EXFont (10.2.0): - ExpoModulesCore - - Expo (46.0.2): + - Expo (46.0.9): - ExpoModulesCore - ExpoKeepAwake (10.2.0): - ExpoModulesCore - - ExpoModulesCore (0.11.3): + - ExpoModulesCore (0.11.4): - React-Core - ReactCommon/turbomodule/core - - EXSplashScreen (0.16.1): + - EXSplashScreen (0.16.2): - ExpoModulesCore - React-Core - - FBLazyVector (0.69.3) - - FBReactNativeSpec (0.69.3): + - FBLazyVector (0.69.4) + - FBReactNativeSpec (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - RCTRequired (= 0.69.3) - - RCTTypeSafety (= 0.69.3) - - React-Core (= 0.69.3) - - React-jsi (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) + - RCTRequired (= 0.69.4) + - RCTTypeSafety (= 0.69.4) + - React-Core (= 0.69.4) + - React-jsi (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) - fmt (6.2.1) - glog (0.3.5) - - hermes-engine (0.69.3) + - hermes-engine (0.69.4) - libevent (2.1.12) - RCT-Folly (2021.06.28.00-v2): - boost @@ -48,214 +48,214 @@ PODS: - fmt (~> 6.2.1) - glog - libevent - - RCTRequired (0.69.3) - - RCTTypeSafety (0.69.3): - - FBLazyVector (= 0.69.3) - - RCTRequired (= 0.69.3) - - React-Core (= 0.69.3) - - React (0.69.3): - - React-Core (= 0.69.3) - - React-Core/DevSupport (= 0.69.3) - - React-Core/RCTWebSocket (= 0.69.3) - - React-RCTActionSheet (= 0.69.3) - - React-RCTAnimation (= 0.69.3) - - React-RCTBlob (= 0.69.3) - - React-RCTImage (= 0.69.3) - - React-RCTLinking (= 0.69.3) - - React-RCTNetwork (= 0.69.3) - - React-RCTSettings (= 0.69.3) - - React-RCTText (= 0.69.3) - - React-RCTVibration (= 0.69.3) - - React-bridging (0.69.3): + - RCTRequired (0.69.4) + - RCTTypeSafety (0.69.4): + - FBLazyVector (= 0.69.4) + - RCTRequired (= 0.69.4) + - React-Core (= 0.69.4) + - React (0.69.4): + - React-Core (= 0.69.4) + - React-Core/DevSupport (= 0.69.4) + - React-Core/RCTWebSocket (= 0.69.4) + - React-RCTActionSheet (= 0.69.4) + - React-RCTAnimation (= 0.69.4) + - React-RCTBlob (= 0.69.4) + - React-RCTImage (= 0.69.4) + - React-RCTLinking (= 0.69.4) + - React-RCTNetwork (= 0.69.4) + - React-RCTSettings (= 0.69.4) + - React-RCTText (= 0.69.4) + - React-RCTVibration (= 0.69.4) + - React-bridging (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - React-jsi (= 0.69.3) - - React-callinvoker (0.69.3) - - React-Codegen (0.69.3): - - FBReactNativeSpec (= 0.69.3) + - React-jsi (= 0.69.4) + - React-callinvoker (0.69.4) + - React-Codegen (0.69.4): + - FBReactNativeSpec (= 0.69.4) - RCT-Folly (= 2021.06.28.00-v2) - - RCTRequired (= 0.69.3) - - RCTTypeSafety (= 0.69.3) - - React-Core (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-Core (0.69.3): + - RCTRequired (= 0.69.4) + - RCTTypeSafety (= 0.69.4) + - React-Core (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-Core (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-Core/Default (= 0.69.3) - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-Core/Default (= 0.69.4) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/CoreModulesHeaders (0.69.3): + - React-Core/CoreModulesHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/Default (0.69.3): + - React-Core/Default (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/DevSupport (0.69.3): + - React-Core/DevSupport (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-Core/Default (= 0.69.3) - - React-Core/RCTWebSocket (= 0.69.3) - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-jsinspector (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-Core/Default (= 0.69.4) + - React-Core/RCTWebSocket (= 0.69.4) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-jsinspector (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTActionSheetHeaders (0.69.3): + - React-Core/RCTActionSheetHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTAnimationHeaders (0.69.3): + - React-Core/RCTAnimationHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTBlobHeaders (0.69.3): + - React-Core/RCTBlobHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTImageHeaders (0.69.3): + - React-Core/RCTImageHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTLinkingHeaders (0.69.3): + - React-Core/RCTLinkingHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTNetworkHeaders (0.69.3): + - React-Core/RCTNetworkHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTSettingsHeaders (0.69.3): + - React-Core/RCTSettingsHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTTextHeaders (0.69.3): + - React-Core/RCTTextHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTVibrationHeaders (0.69.3): + - React-Core/RCTVibrationHeaders (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-Core/RCTWebSocket (0.69.3): + - React-Core/RCTWebSocket (0.69.4): - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-Core/Default (= 0.69.3) - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-Core/Default (= 0.69.4) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-perflogger (= 0.69.4) - Yoga - - React-CoreModules (0.69.3): + - React-CoreModules (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.69.3) - - React-Codegen (= 0.69.3) - - React-Core/CoreModulesHeaders (= 0.69.3) - - React-jsi (= 0.69.3) - - React-RCTImage (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-cxxreact (0.69.3): + - RCTTypeSafety (= 0.69.4) + - React-Codegen (= 0.69.4) + - React-Core/CoreModulesHeaders (= 0.69.4) + - React-jsi (= 0.69.4) + - React-RCTImage (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-cxxreact (0.69.4): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-callinvoker (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsinspector (= 0.69.3) - - React-logger (= 0.69.3) - - React-perflogger (= 0.69.3) - - React-runtimeexecutor (= 0.69.3) - - React-hermes (0.69.3): + - React-callinvoker (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsinspector (= 0.69.4) + - React-logger (= 0.69.4) + - React-perflogger (= 0.69.4) + - React-runtimeexecutor (= 0.69.4) + - React-hermes (0.69.4): - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2021.06.28.00-v2) - RCT-Folly/Futures (= 2021.06.28.00-v2) - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-jsiexecutor (= 0.69.3) - - React-jsinspector (= 0.69.3) - - React-perflogger (= 0.69.3) - - React-jsi (0.69.3): + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-jsiexecutor (= 0.69.4) + - React-jsinspector (= 0.69.4) + - React-perflogger (= 0.69.4) + - React-jsi (0.69.4): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-jsi/Default (= 0.69.3) - - React-jsi/Default (0.69.3): + - React-jsi/Default (= 0.69.4) + - React-jsi/Default (0.69.4): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-jsiexecutor (0.69.3): + - React-jsiexecutor (0.69.4): - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-perflogger (= 0.69.3) - - React-jsinspector (0.69.3) - - React-logger (0.69.3): + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-perflogger (= 0.69.4) + - React-jsinspector (0.69.4) + - React-logger (0.69.4): - glog - react-native-safe-area-context (4.3.1): - RCT-Folly @@ -263,74 +263,76 @@ PODS: - RCTTypeSafety - React - ReactCommon/turbomodule/core - - React-perflogger (0.69.3) - - React-RCTActionSheet (0.69.3): - - React-Core/RCTActionSheetHeaders (= 0.69.3) - - React-RCTAnimation (0.69.3): + - React-perflogger (0.69.4) + - React-RCTActionSheet (0.69.4): + - React-Core/RCTActionSheetHeaders (= 0.69.4) + - React-RCTAnimation (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.69.3) - - React-Codegen (= 0.69.3) - - React-Core/RCTAnimationHeaders (= 0.69.3) - - React-jsi (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-RCTBlob (0.69.3): + - RCTTypeSafety (= 0.69.4) + - React-Codegen (= 0.69.4) + - React-Core/RCTAnimationHeaders (= 0.69.4) + - React-jsi (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-RCTBlob (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - React-Codegen (= 0.69.3) - - React-Core/RCTBlobHeaders (= 0.69.3) - - React-Core/RCTWebSocket (= 0.69.3) - - React-jsi (= 0.69.3) - - React-RCTNetwork (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-RCTImage (0.69.3): + - React-Codegen (= 0.69.4) + - React-Core/RCTBlobHeaders (= 0.69.4) + - React-Core/RCTWebSocket (= 0.69.4) + - React-jsi (= 0.69.4) + - React-RCTNetwork (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-RCTImage (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.69.3) - - React-Codegen (= 0.69.3) - - React-Core/RCTImageHeaders (= 0.69.3) - - React-jsi (= 0.69.3) - - React-RCTNetwork (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-RCTLinking (0.69.3): - - React-Codegen (= 0.69.3) - - React-Core/RCTLinkingHeaders (= 0.69.3) - - React-jsi (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-RCTNetwork (0.69.3): + - RCTTypeSafety (= 0.69.4) + - React-Codegen (= 0.69.4) + - React-Core/RCTImageHeaders (= 0.69.4) + - React-jsi (= 0.69.4) + - React-RCTNetwork (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-RCTLinking (0.69.4): + - React-Codegen (= 0.69.4) + - React-Core/RCTLinkingHeaders (= 0.69.4) + - React-jsi (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-RCTNetwork (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.69.3) - - React-Codegen (= 0.69.3) - - React-Core/RCTNetworkHeaders (= 0.69.3) - - React-jsi (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-RCTSettings (0.69.3): + - RCTTypeSafety (= 0.69.4) + - React-Codegen (= 0.69.4) + - React-Core/RCTNetworkHeaders (= 0.69.4) + - React-jsi (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-RCTSettings (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.69.3) - - React-Codegen (= 0.69.3) - - React-Core/RCTSettingsHeaders (= 0.69.3) - - React-jsi (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-RCTText (0.69.3): - - React-Core/RCTTextHeaders (= 0.69.3) - - React-RCTVibration (0.69.3): + - RCTTypeSafety (= 0.69.4) + - React-Codegen (= 0.69.4) + - React-Core/RCTSettingsHeaders (= 0.69.4) + - React-jsi (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-RCTText (0.69.4): + - React-Core/RCTTextHeaders (= 0.69.4) + - React-RCTVibration (0.69.4): - RCT-Folly (= 2021.06.28.00-v2) - - React-Codegen (= 0.69.3) - - React-Core/RCTVibrationHeaders (= 0.69.3) - - React-jsi (= 0.69.3) - - ReactCommon/turbomodule/core (= 0.69.3) - - React-runtimeexecutor (0.69.3): - - React-jsi (= 0.69.3) - - ReactCommon/turbomodule/core (0.69.3): + - React-Codegen (= 0.69.4) + - React-Core/RCTVibrationHeaders (= 0.69.4) + - React-jsi (= 0.69.4) + - ReactCommon/turbomodule/core (= 0.69.4) + - React-runtimeexecutor (0.69.4): + - React-jsi (= 0.69.4) + - ReactCommon/turbomodule/core (0.69.4): - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-bridging (= 0.69.3) - - React-callinvoker (= 0.69.3) - - React-Core (= 0.69.3) - - React-cxxreact (= 0.69.3) - - React-jsi (= 0.69.3) - - React-logger (= 0.69.3) - - React-perflogger (= 0.69.3) + - React-bridging (= 0.69.4) + - React-callinvoker (= 0.69.4) + - React-Core (= 0.69.4) + - React-cxxreact (= 0.69.4) + - React-jsi (= 0.69.4) + - React-logger (= 0.69.4) + - React-perflogger (= 0.69.4) - RNCAsyncStorage (1.17.7): - React-Core + - RNCMaskedView (0.2.7): + - React-Core - RNGestureHandler (2.5.0): - React-Core - RNReanimated (2.9.1): @@ -363,21 +365,21 @@ PODS: - RNScreens (3.15.0): - React-Core - React-RCTImage - - RNSVG (12.4.3): + - RNSVG (13.0.0): - React-Core - Yoga (1.14.0) DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - "EXApplication (from `../node_modules/.pnpm/expo-application@4.2.2_expo@46.0.2/node_modules/expo-application/ios`)" - - "EXConstants (from `../node_modules/.pnpm/expo-constants@13.2.3_expo@46.0.2/node_modules/expo-constants/ios`)" - - "EXFileSystem (from `../node_modules/.pnpm/expo-file-system@14.1.0_expo@46.0.2/node_modules/expo-file-system/ios`)" - - "EXFont (from `../node_modules/.pnpm/expo-font@10.2.0_expo@46.0.2/node_modules/expo-font/ios`)" - - "Expo (from `../node_modules/.pnpm/expo@46.0.2_@babel+core@7.18.10/node_modules/expo`)" - - "ExpoKeepAwake (from `../node_modules/.pnpm/expo-keep-awake@10.2.0_expo@46.0.2/node_modules/expo-keep-awake/ios`)" - - "ExpoModulesCore (from `../node_modules/.pnpm/expo-modules-core@0.11.3/node_modules/expo-modules-core/ios`)" - - "EXSplashScreen (from `../node_modules/.pnpm/expo-splash-screen@0.16.1_expo@46.0.2/node_modules/expo-splash-screen/ios`)" + - "EXApplication (from `../node_modules/.pnpm/expo-application@4.2.2_expo@46.0.9/node_modules/expo-application/ios`)" + - "EXConstants (from `../node_modules/.pnpm/expo-constants@13.2.3_expo@46.0.9/node_modules/expo-constants/ios`)" + - "EXFileSystem (from `../node_modules/.pnpm/expo-file-system@14.1.0_expo@46.0.9/node_modules/expo-file-system/ios`)" + - "EXFont (from `../node_modules/.pnpm/expo-font@10.2.0_expo@46.0.9/node_modules/expo-font/ios`)" + - "Expo (from `../node_modules/.pnpm/expo@46.0.9_@babel+core@7.18.10/node_modules/expo`)" + - "ExpoKeepAwake (from `../node_modules/.pnpm/expo-keep-awake@10.2.0_expo@46.0.9/node_modules/expo-keep-awake/ios`)" + - "ExpoModulesCore (from `../node_modules/.pnpm/expo-modules-core@0.11.4/node_modules/expo-modules-core/ios`)" + - "EXSplashScreen (from `../node_modules/.pnpm/expo-splash-screen@0.16.2_expo@46.0.9/node_modules/expo-splash-screen/ios`)" - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) @@ -413,6 +415,7 @@ DEPENDENCIES: - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" + - "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)" - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) @@ -430,21 +433,21 @@ EXTERNAL SOURCES: DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXApplication: - :path: "../node_modules/.pnpm/expo-application@4.2.2_expo@46.0.2/node_modules/expo-application/ios" + :path: "../node_modules/.pnpm/expo-application@4.2.2_expo@46.0.9/node_modules/expo-application/ios" EXConstants: - :path: "../node_modules/.pnpm/expo-constants@13.2.3_expo@46.0.2/node_modules/expo-constants/ios" + :path: "../node_modules/.pnpm/expo-constants@13.2.3_expo@46.0.9/node_modules/expo-constants/ios" EXFileSystem: - :path: "../node_modules/.pnpm/expo-file-system@14.1.0_expo@46.0.2/node_modules/expo-file-system/ios" + :path: "../node_modules/.pnpm/expo-file-system@14.1.0_expo@46.0.9/node_modules/expo-file-system/ios" EXFont: - :path: "../node_modules/.pnpm/expo-font@10.2.0_expo@46.0.2/node_modules/expo-font/ios" + :path: "../node_modules/.pnpm/expo-font@10.2.0_expo@46.0.9/node_modules/expo-font/ios" Expo: - :path: "../node_modules/.pnpm/expo@46.0.2_@babel+core@7.18.10/node_modules/expo" + :path: "../node_modules/.pnpm/expo@46.0.9_@babel+core@7.18.10/node_modules/expo" ExpoKeepAwake: - :path: "../node_modules/.pnpm/expo-keep-awake@10.2.0_expo@46.0.2/node_modules/expo-keep-awake/ios" + :path: "../node_modules/.pnpm/expo-keep-awake@10.2.0_expo@46.0.9/node_modules/expo-keep-awake/ios" ExpoModulesCore: - :path: "../node_modules/.pnpm/expo-modules-core@0.11.3/node_modules/expo-modules-core/ios" + :path: "../node_modules/.pnpm/expo-modules-core@0.11.4/node_modules/expo-modules-core/ios" EXSplashScreen: - :path: "../node_modules/.pnpm/expo-splash-screen@0.16.1_expo@46.0.2/node_modules/expo-splash-screen/ios" + :path: "../node_modules/.pnpm/expo-splash-screen@0.16.2_expo@46.0.9/node_modules/expo-splash-screen/ios" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" FBReactNativeSpec: @@ -511,6 +514,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" + RNCMaskedView: + :path: "../node_modules/@react-native-masked-view/masked-view" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" RNReanimated: @@ -529,50 +534,51 @@ SPEC CHECKSUMS: EXConstants: 75c40827af38bd6bfcf69f880a5b45037eeff9c9 EXFileSystem: 927e0a8885aa9c49e50fc38eaba2c2389f2f1019 EXFont: a5d80bd9b3452b2d5abbce2487da89b0150e6487 - Expo: a2d9d4d17b9c97beab797c54220b305708f60e87 + Expo: 73412414e62f5cbc6e713def821de70b92cd3ad6 ExpoKeepAwake: 0e8f18142e71bbf2c7f6aa66ebed249ba1420320 - ExpoModulesCore: 8303cc952788be09fc6eab62815d257016ae6dec - EXSplashScreen: 31ab6df6d23e97e074d1330224741979943f1d82 - FBLazyVector: 1d83d91816fa605d16227a83f1b2e71c8df09d22 - FBReactNativeSpec: 06454fe46192886c1bc472593057015292ba37ee + ExpoModulesCore: e281bb7b78ea47e227dd5af94d04b24d8b2e1255 + EXSplashScreen: 799bece80089219b2c989c1082d70f3b00995cda + FBLazyVector: c71b8c429a8af2aff1013934a7152e9d9d0c937d + FBReactNativeSpec: 3cc5cff7d792e74a875be91e56d6242335016f50 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a - hermes-engine: ff1ba576165861a94a0d101b0a351a8ca2149f36 + hermes-engine: 761a544537e62df2a37189389b9d2654dc1f75af libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a - RCTRequired: 66822c147facf02f7774af99825e0a31e39df42e - RCTTypeSafety: 309306c4e711b14a83c55c2816a6cc490ec19827 - React: a779632422a918b26db4f1b57225a41c14d20525 - React-bridging: 96055aa45f0417898d7833e251f4ae79d28acef7 - React-callinvoker: 02df4d620df286381ff3f99180fb24feceaf01cc - React-Codegen: 06613a5e753c3af2dca0d6e7dd02944a3d77c3f6 - React-Core: 638d54d64048aa635e7c583fb0d8425206f446b4 - React-CoreModules: f706ec2a1939387517cadc6ce0d2ef0f20fccb53 - React-cxxreact: ec183b7f6fec01e7167f38c1c64a03f68dca7fb2 - React-hermes: a97962948f74aaefffd4fe00bdafafbc245b08af - React-jsi: ed7dc77f5193dca9c73cec90bfec409e7ddfe401 - React-jsiexecutor: 1842ca163b160aeb224d2c65b2a60c393b273c67 - React-jsinspector: bb2605f98aada5d81f3494690da3ef3b4ff3b716 - React-logger: 23a50ef4c18bf9adbb51e2c979318e6b3a2e44a1 + RCTRequired: bd9d2ab0fda10171fcbcf9ba61a7df4dc15a28f4 + RCTTypeSafety: e44e139bf6ec8042db396201834fc2372f6a21cd + React: 482cd1ba23c471be1aed3800180be2427418d7be + React-bridging: c2ea4fed6fe4ed27c12fd71e88b5d5d3da107fde + React-callinvoker: d4d1f98163fb5e35545e910415ef6c04796bb188 + React-Codegen: ff35fb9c7f6ec2ed34fb6de2e1099d88dfb25f2f + React-Core: 4d3443a45b67c71d74d7243ddde9569d1e4f4fad + React-CoreModules: 70be25399366b5632ab18ecf6fe444a8165a7bea + React-cxxreact: 822d3794fc0bf206f4691592f90e086dd4f92228 + React-hermes: 7f67b8363288258c3b0cd4aef5975cb7f0b9549a + React-jsi: ffa51cbc9a78cc156cf61f79ed52ecb76dc6013b + React-jsiexecutor: a27badbbdbc0ff781813370736a2d1c7261181d4 + React-jsinspector: 8a3d3f5dcd23a91e8c80b1bf0e96902cd1dca999 + React-logger: 1088859f145b8f6dd0d3ed051a647ef0e3e80fad react-native-safe-area-context: 6c12e3859b6f27b25de4fee8201cfb858432d8de - React-perflogger: 39d2ba8cbcac54d1bb1d9a980dab348e96aef467 - React-RCTActionSheet: b1ad907a2c8f8e4d037148ca507b7f2d6ab1c66d - React-RCTAnimation: 914a9ba46fb6e7376f7709c7ce825d53b47ca2ee - React-RCTBlob: de62fd5edc5c36951f0b113bf252eb43b7131f79 - React-RCTImage: aa0749a8d748b34942c7e71ac5d9f42be8b70cf3 - React-RCTLinking: 595a9f8fbf4d6634bff28d1175b3523b61466612 - React-RCTNetwork: 0559fd0fccb01f89c638baa43c8d185dc8008626 - React-RCTSettings: 8e492a25a62f1ef6323f82ce652ae87fa59c82ca - React-RCTText: 17457cde6ef8832ba43c886baebb6627c5d7ed18 - React-RCTVibration: dd8099eb46e9cee4692934bc8cbe5e9a4f5e8d31 - React-runtimeexecutor: 607eb048e22a16388c908ee1f6644200e8d1e19b - ReactCommon: af7636436b382db7cde4583bbd642f0978e6e3ed + React-perflogger: cb386fd44c97ec7f8199c04c12b22066b0f2e1e0 + React-RCTActionSheet: f803a85e46cf5b4066c2ac5e122447f918e9c6e5 + React-RCTAnimation: 19c80fa950ccce7f4db76a2a7f2cf79baae07fc7 + React-RCTBlob: f36ab97e2d515c36df14a1571e50056be80413d5 + React-RCTImage: 2c8f0a329a116248e82f8972ffe806e47c6d1cfa + React-RCTLinking: 670f0223075aff33be3b89714f1da4f5343fc4af + React-RCTNetwork: 09385b73f4ff1f46bd5d749540fb33f69a7e5908 + React-RCTSettings: 33b12d3ac7a1f2eba069ec7bd1b84345263b3bbe + React-RCTText: a1a3ea902403bd9ae4cf6f7960551dc1d25711b5 + React-RCTVibration: 9adb4a3cbb598d1bbd46a05256f445e4b8c70603 + React-runtimeexecutor: 61ee22a8cdf8b6bb2a7fb7b4ba2cc763e5285196 + ReactCommon: 8f67bd7e0a6afade0f20718f859dc8c2275f2e83 RNCAsyncStorage: d81ee5c3db1060afd49ea7045ad460eff82d2b7d + RNCMaskedView: cb9670ea9239998340eaab21df13fa12a1f9de15 RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50 RNReanimated: 2cf7451318bb9cc430abeec8d67693f9cf4e039c RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7 - RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9 - Yoga: 44c64131616253fa83366295acdbce3d14926041 + RNSVG: 42a0c731b11179ebbd27a3eeeafa7201ebb476ff + Yoga: ff994563b2fd98c982ca58e8cd9db2cdaf4dda74 PODFILE CHECKSUM: b77befb1871220c1a94408eeae0857d78b685698 diff --git a/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj b/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj index 3a8ac6a59..4299b55a6 100644 --- a/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj @@ -3,36 +3,38 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ + 08C620EC6F30A0663310BBC1 /* libPods-Spacedrive.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 080FB394AD883D9705C115A6 /* libPods-Spacedrive.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; - 519D1250147D911454D7DB76 /* libPods-Spacedrive.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F276D840CDBF9D0D9D548D /* libPods-Spacedrive.a */; }; + 5574975428A2496C00851D5A /* SDCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 5574975328A2496C00851D5A /* SDCore.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; C95AE27BB525EFF3F02CEC11 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E71A6EFA9EA8F4C11F42FA /* ExpoModulesProvider.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 080FB394AD883D9705C115A6 /* libPods-Spacedrive.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Spacedrive.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* Spacedrive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Spacedrive.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 5574975328A2496C00851D5A /* SDCore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDCore.m; sourceTree = ""; }; + 5574975528A2498000851D5A /* SDCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDCore.h; sourceTree = ""; }; + 5574975628A24E0D00851D5A /* sdcore-universal-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "sdcore-universal-ios.a"; path = "../../../target/sdcore-universal-ios.a"; sourceTree = ""; }; 56E71A6EFA9EA8F4C11F42FA /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Spacedrive/ExpoModulesProvider.swift"; sourceTree = ""; }; - 6C2E3173556A471DD304B334 /* Pods-mobilenew.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-mobilenew.debug.xcconfig"; path = "Target Support Files/Pods-mobilenew/Pods-mobilenew.debug.xcconfig"; sourceTree = ""; }; - 78E58D73F5113B1BD543EE4D /* Pods-Spacedrive.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Spacedrive.debug.xcconfig"; path = "Target Support Files/Pods-Spacedrive/Pods-Spacedrive.debug.xcconfig"; sourceTree = ""; }; - 7A4D352CD337FB3A3BF06240 /* Pods-mobilenew.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-mobilenew.release.xcconfig"; path = "Target Support Files/Pods-mobilenew/Pods-mobilenew.release.xcconfig"; sourceTree = ""; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SplashScreen.storyboard; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Expo.plist; path = Supporting/Expo.plist; sourceTree = ""; }; - D8F0094C07EEE7528760BD08 /* Pods-Spacedrive.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Spacedrive.release.xcconfig"; path = "Target Support Files/Pods-Spacedrive/Pods-Spacedrive.release.xcconfig"; sourceTree = ""; }; + CD5534017C1F13AB6E57EC53 /* Pods-Spacedrive.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Spacedrive.debug.xcconfig"; path = "Target Support Files/Pods-Spacedrive/Pods-Spacedrive.debug.xcconfig"; sourceTree = ""; }; + E661E5E8069E06E621509713 /* Pods-Spacedrive.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Spacedrive.release.xcconfig"; path = "Target Support Files/Pods-Spacedrive/Pods-Spacedrive.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - F3F276D840CDBF9D0D9D548D /* libPods-Spacedrive.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Spacedrive.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -40,7 +42,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 519D1250147D911454D7DB76 /* libPods-Spacedrive.a in Frameworks */, + 08C620EC6F30A0663310BBC1 /* libPods-Spacedrive.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -58,6 +60,8 @@ 13B07FB61A68108700A75B9A /* Info.plist */, 13B07FB71A68108700A75B9A /* main.m */, AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, + 5574975328A2496C00851D5A /* SDCore.m */, + 5574975528A2498000851D5A /* SDCore.h */, ); path = Spacedrive; sourceTree = ""; @@ -73,8 +77,9 @@ 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { isa = PBXGroup; children = ( + 5574975628A24E0D00851D5A /* sdcore-universal-ios.a */, ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - F3F276D840CDBF9D0D9D548D /* libPods-Spacedrive.a */, + 080FB394AD883D9705C115A6 /* libPods-Spacedrive.a */, ); name = Frameworks; sourceTree = ""; @@ -120,10 +125,8 @@ D65327D7A22EEC0BE12398D9 /* Pods */ = { isa = PBXGroup; children = ( - 6C2E3173556A471DD304B334 /* Pods-mobilenew.debug.xcconfig */, - 7A4D352CD337FB3A3BF06240 /* Pods-mobilenew.release.xcconfig */, - 78E58D73F5113B1BD543EE4D /* Pods-Spacedrive.debug.xcconfig */, - D8F0094C07EEE7528760BD08 /* Pods-Spacedrive.release.xcconfig */, + CD5534017C1F13AB6E57EC53 /* Pods-Spacedrive.debug.xcconfig */, + E661E5E8069E06E621509713 /* Pods-Spacedrive.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -143,14 +146,15 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Spacedrive" */; buildPhases = ( - 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, + 9F553C6F8AA059AB72DAA720 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, + 55B1130D28AB3061006C377F /* Build Spacedrive Core */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, - 0D580678BEFB3E7EF09A2CFE /* [CP] Embed Pods Frameworks */, + C93832A891603A9ED877B5D2 /* [CP] Embed Pods Frameworks */, + 20C3591E751EF7109AA55859 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -221,7 +225,50 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\n`node --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n"; }; - 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = { + 20C3591E751EF7109AA55859 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 55B1130D28AB3061006C377F /* Build Spacedrive Core */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "../../../core/src/*.rs", + "../rust/src/*.rs", + "../../../core/src/*/*.rs", + ); + name = "Build Spacedrive Core"; + outputFileListPaths = ( + ); + outputPaths = ( + "../../../target/sdcore-universal-ios.a", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/zsh; + shellScript = "set -e\n\nif [[ -n \"${DEVELOPER_SDK_DIR:-}\" ]]; then\n # Assume we're in Xcode, which means we're probably cross-compiling.\n # In this case, we need to add an extra library search path for build scripts and proc-macros,\n # which run on the host instead of the target.\n # (macOS Big Sur does not have linkable libraries in /usr/lib/.)\n export LIBRARY_PATH=\"${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}\"\nfi\n\nCARGO_FLAGS=\nif [[ \"$BUILDVARIANT\" != \"debug\" ]]; then\n CARGO_FLAGS=--release\nfi\n\nTARGET_DIRECTORY=../../../target\nif [[ $PLATFORM_NAME = \"iphonesimulator\" ]]\nthen\n cargo build -p sdcore-lib $CARGO_FLAGS --lib --target aarch64-apple-ios-sim\n lipo -create -output $TARGET_DIRECTORY/libsdcore-iossim.a $TARGET_DIRECTORY/aarch64-apple-ios-sim/release/libsdcore.a\nelse\n cargo build -p sdcore-lib $CARGO_FLAGS --lib --target aarch64-apple-ios\n lipo -create -output $TARGET_DIRECTORY/libsdcore-ios.a $TARGET_DIRECTORY/aarch64-apple-ios/release/libsdcore.a\nfi\n"; + }; + 9F553C6F8AA059AB72DAA720 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -243,7 +290,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 0D580678BEFB3E7EF09A2CFE /* [CP] Embed Pods Frameworks */ = { + C93832A891603A9ED877B5D2 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -261,26 +308,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; FD10A7F022414F080027D42C /* Start Packager */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -310,6 +337,7 @@ 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, C95AE27BB525EFF3F02CEC11 /* ExpoModulesProvider.swift in Sources */, + 5574975428A2496C00851D5A /* SDCore.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -318,7 +346,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 78E58D73F5113B1BD543EE4D /* Pods-Spacedrive.debug.xcconfig */; + baseConfigurationReference = CD5534017C1F13AB6E57EC53 /* Pods-Spacedrive.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; @@ -333,12 +361,73 @@ ); INFOPLIST_FILE = Spacedrive/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/DoubleConversion\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/EXApplication\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/EXFont\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Expo\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ExpoKeepAwake\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ExpoModulesCore\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RCTTypeSafety\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNCMaskedView\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNGestureHandler\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNReanimated\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNSVG\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNScreens\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Codegen\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Core\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-CoreModules\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTAnimation\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTBlob\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTImage\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTLinking\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTNetwork\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTSettings\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTText\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTVibration\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-bridging\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-hermes\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsi\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsiexecutor\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsinspector\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-logger\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-perflogger\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Yoga\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/fmt\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/glog\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/libevent\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-safe-area-context\"", + /usr/lib/swift, + ../../../target, + ); OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", "-lc++", ); + "OTHER_LDFLAGS[sdk=iphoneos*]" = ( + "$(inherited)", + "-ObjC", + "-lc++", + "-lsdcore-ios", + ); + "OTHER_LDFLAGS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "-ObjC", + "-lc++", + "-lsdcore-iossim", + ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.spacedrive.app; PRODUCT_NAME = Spacedrive; @@ -351,7 +440,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D8F0094C07EEE7528760BD08 /* Pods-Spacedrive.release.xcconfig */; + baseConfigurationReference = E661E5E8069E06E621509713 /* Pods-Spacedrive.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; @@ -361,12 +450,73 @@ DEVELOPMENT_TEAM = 72SE38W7T9; INFOPLIST_FILE = Spacedrive/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/DoubleConversion\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/EXApplication\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/EXFont\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Expo\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ExpoKeepAwake\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ExpoModulesCore\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RCTTypeSafety\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNCMaskedView\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNGestureHandler\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNReanimated\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNSVG\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNScreens\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Codegen\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Core\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-CoreModules\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTAnimation\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTBlob\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTImage\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTLinking\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTNetwork\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTSettings\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTText\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTVibration\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-bridging\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-hermes\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsi\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsiexecutor\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsinspector\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-logger\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-perflogger\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Yoga\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/fmt\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/glog\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/libevent\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-safe-area-context\"", + /usr/lib/swift, + ../../../target, + ); OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", "-lc++", ); + "OTHER_LDFLAGS[sdk=iphoneos*]" = ( + "$(inherited)", + "-ObjC", + "-lc++", + "-lsdcore-ios", + ); + "OTHER_LDFLAGS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "-ObjC", + "-lc++", + "-lsdcore-iossim", + ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.spacedrive.app; PRODUCT_NAME = Spacedrive; @@ -425,7 +575,10 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -476,7 +629,10 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; diff --git a/apps/mobile/ios/Spacedrive/Info.plist b/apps/mobile/ios/Spacedrive/Info.plist index 05ff2178f..d9d78a1b6 100644 --- a/apps/mobile/ios/Spacedrive/Info.plist +++ b/apps/mobile/ios/Spacedrive/Info.plist @@ -1,77 +1,81 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Spacedrive - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 0.0.1 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleURLSchemes - - spacedrive - com.spacedrive.app - - - - CFBundleVersion - 1 - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSExceptionDomains - - localhost - - NSExceptionAllowsInsecureHTTPLoads - - - - - UILaunchStoryboardName - SplashScreen - UIRequiredDeviceCapabilities - - armv7 - - UIRequiresFullScreen - - UIStatusBarStyle - UIStatusBarStyleDefault - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIUserInterfaceStyle - Automatic - UIViewControllerBasedStatusBarAppearance - - - \ No newline at end of file + + UIBackgroundModes + + remote-notification + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Spacedrive + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 0.0.1 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + spacedrive + com.spacedrive.app + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + UILaunchStoryboardName + SplashScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Automatic + UIViewControllerBasedStatusBarAppearance + + + diff --git a/apps/mobile/ios/Spacedrive/SDCore.h b/apps/mobile/ios/Spacedrive/SDCore.h new file mode 100644 index 000000000..cdcac491d --- /dev/null +++ b/apps/mobile/ios/Spacedrive/SDCore.h @@ -0,0 +1,17 @@ +// +// SDCore.h +// Spacedrive +// +// Created by Oscar Beaumont on 9/8/2022. +// + +#import +#import + +#ifndef SDCore_h +#define SDCore_h + +@interface SDCore : RCTEventEmitter +@end + +#endif /* SDCore_h */ diff --git a/apps/mobile/ios/Spacedrive/SDCore.m b/apps/mobile/ios/Spacedrive/SDCore.m new file mode 100644 index 000000000..969b2c106 --- /dev/null +++ b/apps/mobile/ios/Spacedrive/SDCore.m @@ -0,0 +1,81 @@ +// +// SDCore.m +// Spacedrive +// +// This file will not work unless ARC is disabled. Do this by setting the compiler flag '-fno-objc-arc' on this file in Settings > Build Phases > Compile Sources. +// This file also expects the Spacedrive Rust library to be linked in Settings > Build Phases > Link Binary with Libraries. This is the crude way, you should link the core with custom linker flags so that you can do it conditonally based on target OS. +// This file also expects a Build Phase to be setup which compiles the Rust prior to linking the sdcore library to the IOS build. +// This file also expects you to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist +// +// Created by Oscar Beaumont on 9/8/2022. +// + +#import "SDCore.h" +#import + +// is a function defined in Rust which starts a listener for Rust events. +void register_core_event_listener(id objc_class); + +// is a function defined in Rust which is responsible for handling messages from the frontend. +void sd_core_msg(const char* query, void* resolve); + +// is called by Rust to determine the base directory to store data in. This is only done when initialising the Node. +const char* get_data_directory(void) +{ + NSArray *dirPaths = dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, + NSUserDomainMask, YES); + const char *docDir = [ [dirPaths objectAtIndex:0] UTF8String]; + return docDir; +} + +// is called by Rust with a void* to the resolve function (of type RCTPromiseResolveBlock) to call it. +// Due to 'RCTPromiseResolveBlock' being an Objective-C block it is hard to call from Rust. +void call_resolve(void *resolvePtr, const char* resultRaw) +{ + RCTPromiseResolveBlock resolve = (__bridge RCTPromiseResolveBlock) resolvePtr; + NSString *result = [NSString stringWithUTF8String:resultRaw]; + resolve(result); + [result release]; +} + +@implementation SDCore +{ + bool registeredWithRust; + bool hasListeners; +} + +-(void)startObserving { + if (!registeredWithRust) + { + register_core_event_listener(self); + registeredWithRust = true; + } + + hasListeners = YES; +} + +-(void)stopObserving { + hasListeners = NO; +} + +- (void)sendCoreEvent: (NSString*)query { + if (hasListeners) { + [self sendEventWithName:@"SDCoreEvent" body:query]; + } +} + +- (NSArray *)supportedEvents { + return @[@"SDCoreEvent"]; +} + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(sd_core_msg: (NSString *)queryRaw + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + const char *query = [queryRaw UTF8String]; + sd_core_msg(query, (__bridge void*) [resolve retain]); +} + +@end diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js index c7768fd74..286802d1b 100644 --- a/apps/mobile/metro.config.js +++ b/apps/mobile/metro.config.js @@ -3,7 +3,6 @@ const MetroSymlinksResolver = require('@rnx-kit/metro-resolver-symlinks'); // Might not need these anymore. const [SDAssetsPath, SDAssetsPathExclude] = resolveUniqueModule('@sd/assets', '.'); -const [SDCorePath, SDCorePathExclude] = resolveUniqueModule('@sd/core', '.'); const [babelRuntimePath, babelRuntimeExclude] = resolveUniqueModule('@babel/runtime'); const [reactPath, reactExclude] = resolveUniqueModule('react'); @@ -21,7 +20,6 @@ const metroConfig = makeMetroConfig({ extraNodeModules: { '@babel/runtime': babelRuntimePath, '@sd/assets': SDAssetsPath, - '@sd/core': SDCorePath, 'react': reactPath, 'react-native-svg': reactSVGPath }, @@ -29,7 +27,6 @@ const metroConfig = makeMetroConfig({ blockList: exclusionList([ babelRuntimeExclude, SDAssetsPathExclude, - SDCorePathExclude, reactExclude, reactSVGExclude ]), diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 9fe4e2594..711926bd7 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -4,7 +4,7 @@ "main": "index.js", "license": "GPL-3.0-only", "scripts": { - "dev": "expo start --dev-client", + "start": "expo start --dev-client", "android": "expo run:android", "ios": "expo run:ios", "lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit" @@ -12,42 +12,45 @@ "dependencies": { "@expo/vector-icons": "^13.0.0", "@gorhom/bottom-sheet": "^4.4.3", - "@react-native-async-storage/async-storage": "^1.17.7", - "@react-navigation/bottom-tabs": "^6.3.2", - "@react-navigation/drawer": "^6.4.3", - "@react-navigation/native": "^6.0.11", - "@react-navigation/native-stack": "^6.7.0", + "@react-native-async-storage/async-storage": "~1.17.3", + "@react-native-masked-view/masked-view": "0.2.7", + "@react-navigation/bottom-tabs": "^6.3.3", + "@react-navigation/drawer": "^6.4.4", + "@react-navigation/native": "^6.0.12", + "@react-navigation/stack": "^6.2.3", + "@rspc/client": "^0.0.5", "@sd/assets": "file:../../packages/assets", - "@sd/core": "file:../../core", + "@tanstack/react-query": "^4.2.3", "byte-size": "^8.1.0", "class-variance-authority": "^0.2.3", - "expo": "~46.0.2", - "expo-font": "^10.2.0", - "expo-linking": "^3.2.2", - "expo-splash-screen": "~0.16.1", + "date-fns": "^2.29.2", + "expo": "~46.0.9", + "expo-font": "~10.2.0", + "expo-linking": "~3.2.2", + "expo-splash-screen": "~0.16.2", "expo-status-bar": "~1.4.0", "intl": "^1.2.5", "moti": "^0.18.0", "phosphor-react-native": "^1.1.2", - "react": "18.2.0", - "react-native": "0.69.3", - "react-native-gesture-handler": "^2.5.0", + "react": "18.0.0", + "react-native": "0.69.4", + "react-native-gesture-handler": "~2.5.0", "react-native-heroicons": "^2.2.0", - "react-native-reanimated": "^2.9.1", - "react-native-safe-area-context": "^4.3.1", - "react-native-screens": "^3.15.0", - "react-native-svg": "^12.3.0", + "react-native-reanimated": "~2.9.1", + "react-native-safe-area-context": "4.3.1", + "react-native-screens": "~3.15.0", + "react-native-svg": "13.0.0", "twrnc": "^3.4.0", "use-count-up": "^3.0.1", - "zustand": "^4.0.0" + "zustand": "^4.1.1" }, "devDependencies": { - "@babel/core": "^7.12.9", + "@babel/core": "^7.18.6", "@babel/runtime": "^7.18.9", "@rnx-kit/metro-config": "^1.2.36", "@rnx-kit/metro-resolver-symlinks": "^0.1.21", - "@types/react": "~18.0.15", - "@types/react-native": "~0.69.5", + "@types/react": "~18.0.0", + "@types/react-native": "~0.69.1", "@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/parser": "^5.30.7", "eslint": "^8.21.0", @@ -55,8 +58,7 @@ "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-native": "^4.0.0", - "metro-minify-terser": "^0.72.0", - "react-native-svg": "^12.4.3", + "metro-minify-terser": "^0.72.1", "react-native-svg-transformer": "^1.0.0", "typescript": "^4.7.4" }, diff --git a/apps/mobile/pnpm-lock.yaml b/apps/mobile/pnpm-lock.yaml index 1cf083f98ee040cefbbe37285f0b834a66c23d33..75295d7a7edc621dca97cf736398368a1f2d7935 100644 GIT binary patch delta 12747 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 delta 10117 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 diff --git a/apps/mobile/rust/Cargo.toml b/apps/mobile/rust/Cargo.toml new file mode 100644 index 000000000..d2140b3c6 --- /dev/null +++ b/apps/mobile/rust/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "sdcore-lib" +version = "0.1.0" +edition = "2021" +rust-version = "1.63.0" + +[lib] +name = "sdcore" +crate-type = ["staticlib", "cdylib"] # staticlib for IOS and cdylib for Android + +[dependencies] +once_cell = "1.13.0" +sdcore = { path = "../../../core", features = ["mobile", "p2p"], default-features = false } +rspc = { version = "0.0.4", features = [] } +serde_json = "1.0.83" +tokio = "1.20.1" +openssl = { version = "0.10.41", features = ["vendored"] } # Override features of transitive dependencies +openssl-sys = { version = "0.9.75", features = ["vendored"] } # Override features of transitive dependencies to support IOS Simulator on M1 + +[target.'cfg(target_os = "ios")'.dependencies] +objc = "0.2.7" +objc_id = "0.1.1" +objc-foundation = "0.1.1" + +# This is `not(ios)` instead of `android` because of https://github.com/mozilla/rust-android-gradle/issues/93 +[target.'cfg(not(target_os = "ios"))'.dependencies] +jni = "0.19.0" diff --git a/apps/mobile/rust/src/android.rs b/apps/mobile/rust/src/android.rs new file mode 100644 index 000000000..3f51a5d49 --- /dev/null +++ b/apps/mobile/rust/src/android.rs @@ -0,0 +1,111 @@ +use crate::{CLIENT_CONTEXT, EVENT_SENDER, NODE, RUNTIME}; +use jni::objects::{JClass, JObject, JString}; +use jni::JNIEnv; +use rspc::Request; +use sdcore::Node; +use tokio::sync::mpsc::unbounded_channel; + +#[no_mangle] +pub extern "system" fn Java_com_spacedrive_app_SDCore_registerCoreEventListener( + env: JNIEnv, + class: JClass, +) { + let jvm = env.get_java_vm().unwrap(); + let class = env.new_global_ref(class).unwrap(); + let (tx, mut rx) = unbounded_channel(); + let _ = EVENT_SENDER.set(tx); + + RUNTIME.spawn(async move { + while let Some(event) = rx.recv().await { + let data = match serde_json::to_string(&event) { + Ok(json) => json, + Err(err) => { + println!("Failed to serialize event: {}", err); + continue; + }, + }; + + let env = jvm.attach_current_thread().unwrap(); + env.call_method( + &class, + "sendCoreEvent", + "(Ljava/lang/String;)V", + &[env + .new_string(data) + .expect("Couldn't create java string!") + .into()], + ) + .unwrap(); + } + }); +} + +#[no_mangle] +pub extern "system" fn Java_com_spacedrive_app_SDCore_handleCoreMsg( + env: JNIEnv, + class: JClass, + query: JString, + callback: JObject, +) { + let jvm = env.get_java_vm().unwrap(); + let query: String = env + .get_string(query) + .expect("Couldn't get java string!") + .into(); + let class = env.new_global_ref(class).unwrap(); + let callback = env.new_global_ref(callback).unwrap(); + + RUNTIME.spawn(async move { + let request: Request = serde_json::from_str(&query).unwrap(); + + let node = &mut *NODE.lock().await; + let (node, router) = match node { + Some(node) => node.clone(), + None => { + let data_dir: String = { + let env = jvm.attach_current_thread().unwrap(); + let data_dir = env + .call_method( + &class, + "getDataDirectory", + "()Ljava/lang/String;", + &[], + ) + .unwrap() + .l() + .unwrap(); + + env.get_string(data_dir.into()).unwrap().into() + }; + + let new_node = Node::new(data_dir).await; + node.replace(new_node.clone()); + new_node + }, + }; + + let resp = serde_json::to_string( + &request + .handle( + node.get_request_context(), + &router, + &CLIENT_CONTEXT, + EVENT_SENDER.get(), + ) + .await, + ) + .unwrap(); + + let env = jvm.attach_current_thread().unwrap(); + env.call_method( + &callback, + "resolve", + "(Ljava/lang/Object;)V", + &[env + .new_string(resp) + .expect("Couldn't create java string!") + .into()], + ) + .unwrap(); + }); +} diff --git a/apps/mobile/rust/src/ios.rs b/apps/mobile/rust/src/ios.rs new file mode 100644 index 000000000..9abcc7c87 --- /dev/null +++ b/apps/mobile/rust/src/ios.rs @@ -0,0 +1,96 @@ +use crate::{CLIENT_CONTEXT, EVENT_SENDER, NODE, RUNTIME}; +use std::{ + ffi::{CStr, CString}, + os::raw::{c_char, c_void}, +}; +use tokio::sync::mpsc::unbounded_channel; + +use objc::{class, msg_send, runtime::Object, sel, sel_impl}; +use objc_foundation::{INSString, NSString}; +use objc_id::Id; +use rspc::Request; +use sdcore::Node; + +extern "C" { + fn get_data_directory() -> *const c_char; + fn call_resolve(resolve: *const c_void, result: *const c_char); +} + +// This struct wraps the function pointer which represent a Javascript Promise. We wrap the +// function pointers in a struct so we can unsafely assert to Rust that they are `Send`. +// We know they are send as we have ensured Objective-C won't deallocate the function pointer +// until `call_resolve` is called. +struct RNPromise(*const c_void); + +unsafe impl Send for RNPromise {} + +impl RNPromise { + // resolve the promise + unsafe fn resolve(self, result: CString) { + call_resolve(self.0, result.as_ptr()); + } +} + +#[no_mangle] +pub unsafe extern "C" fn register_core_event_listener(id: *mut Object) { + let id = Id::::from_ptr(id); + + let (tx, mut rx) = unbounded_channel(); + let _ = EVENT_SENDER.set(tx); + + RUNTIME.spawn(async move { + while let Some(event) = rx.recv().await { + let data = match serde_json::to_string(&event) { + Ok(json) => json, + Err(err) => { + println!("Failed to serialize event: {}", err); + continue; + }, + }; + let data = NSString::from_str(&data); + let _: () = msg_send![id, sendCoreEvent: data]; + } + }); +} + +#[no_mangle] +pub unsafe extern "C" fn sd_core_msg(query: *const c_char, resolve: *const c_void) { + // This string is cloned to the Rust heap. This is important as Objective-C may remove the query once this function completions but prior to the async block finishing. + let query = CStr::from_ptr(query).to_str().unwrap().to_string(); + + let resolve = RNPromise(resolve); + RUNTIME.spawn(async move { + let request: Request = serde_json::from_str(&query).unwrap(); + + let node = &mut *NODE.lock().await; + let (node, router) = match node { + Some(node) => node.clone(), + None => { + let doc_dir = CStr::from_ptr(get_data_directory()) + .to_str() + .unwrap() + .to_string(); + let new_node = Node::new(doc_dir).await; + node.replace(new_node.clone()); + new_node + }, + }; + + resolve.resolve( + CString::new( + serde_json::to_vec( + &request + .handle( + node.get_request_context(), + &router, + &CLIENT_CONTEXT, + EVENT_SENDER.get(), + ) + .await, + ) + .unwrap(), + ) + .unwrap(), + ) + }); +} diff --git a/apps/mobile/rust/src/lib.rs b/apps/mobile/rust/src/lib.rs new file mode 100644 index 000000000..10c476149 --- /dev/null +++ b/apps/mobile/rust/src/lib.rs @@ -0,0 +1,31 @@ +use std::sync::Arc; + +use once_cell::sync::{Lazy, OnceCell}; +use rspc::{ClientContext, Response}; +use sdcore::{api::Router, Node}; +use tokio::{ + runtime::Runtime, + sync::{mpsc::UnboundedSender, Mutex}, +}; + +#[allow(dead_code)] +pub(crate) static RUNTIME: Lazy = Lazy::new(|| Runtime::new().unwrap()); + +#[allow(dead_code)] +pub(crate) static NODE: Lazy, Arc)>>> = + Lazy::new(|| Mutex::new(None)); + +#[allow(dead_code)] +pub(crate) static CLIENT_CONTEXT: Lazy = Lazy::new(|| ClientContext { + subscriptions: Default::default(), +}); + +#[allow(dead_code)] +pub(crate) static EVENT_SENDER: OnceCell> = OnceCell::new(); + +#[cfg(target_os = "ios")] +mod ios; + +/// This is `not(ios)` instead of `android` because of https://github.com/mozilla/rust-android-gradle/issues/93 +#[cfg(not(target_os = "ios"))] +mod android; diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index 2995de696..14794ffd7 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -1,18 +1,26 @@ +import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; import { DefaultTheme, NavigationContainer, Theme } from '@react-navigation/native'; +import { createClient } from '@rspc/client'; import { StatusBar } from 'expo-status-bar'; import React, { useEffect } from 'react'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useDeviceContext } from 'twrnc'; +import { GlobalModals } from './components/modals/GlobalModals'; +import { ReactNativeTransport, queryClient, rspc, useInvalidateQuery } from './hooks/rspc'; import useCachedResources from './hooks/useCachedResources'; import { getItemFromStorage } from './lib/storage'; import tw from './lib/tailwind'; import RootNavigator from './navigation'; import OnboardingNavigator from './navigation/OnboardingNavigator'; import { useOnboardingStore } from './stores/useOnboardingStore'; +import type { Operations } from './types/bindings'; + +const client = createClient({ + transport: new ReactNativeTransport() +}); -// const NavigatorTheme: Theme = { ...DefaultTheme, colors: { @@ -41,14 +49,27 @@ export default function App() { return null; } else { return ( - - - - {showOnboarding ? : } - - - - + + <> + + + + + + + {showOnboarding ? : } + + + + + + + ); } } + +function InvalidateQuery() { + useInvalidateQuery(); + return null; +} diff --git a/apps/mobile/src/assets/temp/folder-white.svg b/apps/mobile/src/assets/temp/folder-white.svg deleted file mode 100644 index 069ff8b01..000000000 --- a/apps/mobile/src/assets/temp/folder-white.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/apps/mobile/src/assets/temp/folder.svg b/apps/mobile/src/assets/temp/folder.svg deleted file mode 100644 index 5b9be3c5e..000000000 --- a/apps/mobile/src/assets/temp/folder.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/apps/mobile/src/components/base/Divider.tsx b/apps/mobile/src/components/base/Divider.tsx new file mode 100644 index 000000000..f936c2884 --- /dev/null +++ b/apps/mobile/src/components/base/Divider.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { StyleProp, Text, View, ViewStyle } from 'react-native'; + +import tw from '../../lib/tailwind'; + +type DividerProps = { + style?: StyleProp; +}; + +const Divider = ({ style }: DividerProps) => { + return ; +}; + +export default Divider; diff --git a/apps/mobile/src/components/browse/BrowseLocationItem.tsx b/apps/mobile/src/components/browse/BrowseLocationItem.tsx index 383b50624..2298925c9 100644 --- a/apps/mobile/src/components/browse/BrowseLocationItem.tsx +++ b/apps/mobile/src/components/browse/BrowseLocationItem.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Pressable, Text, View } from 'react-native'; import tw from '../../lib/tailwind'; -import FolderIcon from '../icons/Folder'; +import FolderIcon from '../icons/FolderIcon'; interface BrowseLocationItemProps { folderName: string; diff --git a/apps/mobile/src/components/device/Device.tsx b/apps/mobile/src/components/device/Device.tsx index 3eb626d82..d8a24afad 100644 --- a/apps/mobile/src/components/device/Device.tsx +++ b/apps/mobile/src/components/device/Device.tsx @@ -1,19 +1,12 @@ -import { FilePath } from '@sd/core'; import { Cloud, Desktop, DeviceMobileCamera, Laptop } from 'phosphor-react-native'; import React from 'react'; import { FlatList, Text, View } from 'react-native'; import { LockClosedIcon } from 'react-native-heroicons/solid'; import tw from '../../lib/tailwind'; +import { FilePath } from '../../types/bindings'; import FileItem from '../file/FileItem'; -export interface DeviceProps { - name: string; - size: string; - type: 'laptop' | 'desktop' | 'phone' | 'server'; - locations: Array<{ name: string; folder?: boolean; format?: string; icon?: string }>; -} - const placeholderFileItems: FilePath[] = [ { is_dir: true, @@ -272,10 +265,18 @@ const placeholderFileItems: FilePath[] = [ } ]; +export interface DeviceProps { + name: string; + size: string; + type: 'laptop' | 'desktop' | 'phone' | 'server'; + locations: { name: string; folder?: boolean; format?: string; icon?: string }[]; + runningJob?: { amount: number; task: string }; +} + const Device = ({ name, locations, size, type }: DeviceProps) => { return ( - + {type === 'phone' && ( @@ -299,7 +300,7 @@ const Device = ({ name, locations, size, type }: DeviceProps) => { renderItem={({ item }) => } keyExtractor={(item) => item.id.toString()} horizontal - contentContainerStyle={tw`mt-4 ml-2`} + contentContainerStyle={tw`mt-3 mb-5`} showsHorizontalScrollIndicator={false} /> diff --git a/apps/mobile/src/components/drawer/DrawerContent.tsx b/apps/mobile/src/components/drawer/DrawerContent.tsx index 9f57c21d9..0a727600b 100644 --- a/apps/mobile/src/components/drawer/DrawerContent.tsx +++ b/apps/mobile/src/components/drawer/DrawerContent.tsx @@ -1,56 +1,110 @@ import { DrawerContentScrollView } from '@react-navigation/drawer'; import { DrawerContentComponentProps } from '@react-navigation/drawer/lib/typescript/src/types'; -import { House } from 'phosphor-react-native'; +import { getFocusedRouteNameFromRoute } from '@react-navigation/native'; import React from 'react'; -import { Pressable, Text, View } from 'react-native'; +import { ColorValue, Platform, Pressable, Text, View } from 'react-native'; import { CogIcon } from 'react-native-heroicons/solid'; import Layout from '../../constants/Layout'; import tw from '../../lib/tailwind'; -import type { DrawerNavParamList } from '../../navigation/DrawerNavigator'; -import { valueof } from '../../types/helper'; -import DrawerItem from './DrawerItem'; +import CollapsibleView from '../layout/CollapsibleView'; +import DrawerLocationItem from './DrawerLocationItem'; +import DrawerLogo from './DrawerLogo'; +import DrawerTagItem from './DrawerTagItem'; -const drawerHeight = Layout.window.height * 0.85; +const placeholderLocationData = [ + { + id: 1, + name: 'Spacedrive' + }, + { + id: 2, + name: 'Content' + } +]; +const placeholderTagsData = [ + { + id: 1, + name: 'Funny', + color: tw.color('blue-500') + }, + { + id: 2, + name: 'Twitch', + color: tw.color('purple-500') + }, + { + id: 3, + name: 'BlackMagic', + color: tw.color('red-500') + } +]; -// This is a hacky way to get the active route name and params but it works and it's typed... +const drawerHeight = Platform.select({ + ios: Layout.window.height * 0.85, + android: Layout.window.height * 0.9 +}); -interface ActiveRoute { - key: string; - name: keyof DrawerNavParamList; - params: valueof>; -} - -const getActiveRouteState = function (state: any): ActiveRoute { +const getActiveRouteState = function (state: any) { if (!state.routes || state.routes.length === 0 || state.index >= state.routes.length) { return state; } - const childActiveRoute = state.routes[state.index]; return getActiveRouteState(childActiveRoute); }; -// Overriding the default to add typing for our params. -// interface DrawerContentComponentProps { -// state: DrawerNavigationState; -// navigation: NavigationHelpers & -// DrawerActionHelpers; -// // descriptors type is generic -// descriptors: DrawerDescriptorMap; -// } +const DrawerContent = ({ navigation, state }: DrawerContentComponentProps) => { + const stackName = getFocusedRouteNameFromRoute(getActiveRouteState(state)) ?? 'OverviewStack'; -const DrawerContent = ({ descriptors, navigation, state }: DrawerContentComponentProps) => { return ( - + - TODO: Library Selection - } - onPress={() => navigation.jumpTo('Home')} - isSelected={getActiveRouteState(state).name === 'Home'} - /> + + TODO: Library Selection + {/* Locations */} + + {placeholderLocationData.map((location) => ( + + navigation.navigate(stackName, { + screen: 'Location', + params: { id: location.id } + }) + } + /> + ))} + {/* Add Location */} + + + Add Location + + + + {/* Tags */} + + {placeholderTagsData.map((tag) => ( + + navigation.navigate(stackName, { + screen: 'Tag', + params: { id: tag.id } + }) + } + tagColor={tag.color as ColorValue} + /> + ))} + {/* Settings */} navigation.navigate('Settings')}> diff --git a/apps/mobile/src/components/drawer/DrawerItem.tsx b/apps/mobile/src/components/drawer/DrawerItem.tsx deleted file mode 100644 index 99daf69e9..000000000 --- a/apps/mobile/src/components/drawer/DrawerItem.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { Pressable, Text, View } from 'react-native'; - -import tw from '../../lib/tailwind'; - -interface DrawerProps { - label: string; - onPress: () => void; - icon: JSX.Element; - isSelected: boolean; -} - -const DrawerItem: React.FC = (props) => { - const { label, icon, onPress, isSelected } = props; - return ( - - - {icon} - - {label} - - - - ); -}; - -export default DrawerItem; diff --git a/apps/mobile/src/components/drawer/DrawerLocationItem.tsx b/apps/mobile/src/components/drawer/DrawerLocationItem.tsx new file mode 100644 index 000000000..a9f74ae74 --- /dev/null +++ b/apps/mobile/src/components/drawer/DrawerLocationItem.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Pressable, Text, View } from 'react-native'; + +import tw from '../../lib/tailwind'; +import FolderIcon from '../icons/FolderIcon'; + +interface DrawerLocationItemProps { + folderName: string; + onPress: () => void; +} + +const DrawerLocationItem: React.FC = (props) => { + const { folderName, onPress } = props; + return ( + + + + + {folderName} + + + + ); +}; + +export default DrawerLocationItem; diff --git a/apps/mobile/src/components/drawer/DrawerLogo.tsx b/apps/mobile/src/components/drawer/DrawerLogo.tsx new file mode 100644 index 000000000..afe1ce54c --- /dev/null +++ b/apps/mobile/src/components/drawer/DrawerLogo.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Image, Text, View } from 'react-native'; + +import tw from '../../lib/tailwind'; +import Divider from '../base/Divider'; + +const DrawerLogo = () => { + return ( + <> + + + Spacedrive + + + + ); +}; + +export default DrawerLogo; diff --git a/apps/mobile/src/components/drawer/DrawerScreenWrapper.tsx b/apps/mobile/src/components/drawer/DrawerScreenWrapper.tsx deleted file mode 100644 index 3bc677b40..000000000 --- a/apps/mobile/src/components/drawer/DrawerScreenWrapper.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useDrawerProgress } from '@react-navigation/drawer'; -import React from 'react'; -import Animated, { Extrapolate, interpolate, useAnimatedStyle } from 'react-native-reanimated'; -import { SafeAreaView } from 'react-native-safe-area-context'; - -import tw from '../../lib/tailwind'; - -// NOTE: Only wrap screens that need the drawer to be open. (Only Overview Screen for now.) -const DrawerScreenWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const progress: any = useDrawerProgress(); - - const style = useAnimatedStyle(() => { - // TODO: Fix this, it looks weird. Especially on Android. translateX/Y might be the cause. - const scale = interpolate(progress.value, [0, 1], [1, 0.88], Extrapolate.CLAMP); - const translateX = interpolate(progress.value, [0, 1], [0, -12], Extrapolate.CLAMP); - const translateY = interpolate(progress.value, [0, 1], [0, 12], Extrapolate.CLAMP); - const borderRadius = interpolate(progress.value, [0, 1], [0, 16], Extrapolate.CLAMP); - return { - transform: [{ scale }, { translateX }, { translateY }], - borderRadius - }; - }, []); - - return ( - - {children} - - ); -}; - -export default DrawerScreenWrapper; diff --git a/apps/mobile/src/components/drawer/DrawerTagItem.tsx b/apps/mobile/src/components/drawer/DrawerTagItem.tsx new file mode 100644 index 000000000..210b04876 --- /dev/null +++ b/apps/mobile/src/components/drawer/DrawerTagItem.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { ColorValue, Pressable, Text, View } from 'react-native'; + +import tw from '../../lib/tailwind'; + +type DrawerTagItemProps = { + tagName: string; + tagColor: ColorValue; + onPress: () => void; +}; + +const DrawerTagItem: React.FC = (props) => { + const { tagName, tagColor, onPress } = props; + return ( + + + + + {tagName} + + + + ); +}; + +export default DrawerTagItem; diff --git a/apps/mobile/src/components/file/FileIcon.tsx b/apps/mobile/src/components/file/FileIcon.tsx new file mode 100644 index 000000000..101f78dc9 --- /dev/null +++ b/apps/mobile/src/components/file/FileIcon.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { Text, View } from 'react-native'; +import Svg, { Path } from 'react-native-svg'; + +import icons from '../../assets/icons/file'; +import tw from '../../lib/tailwind'; +import { FilePath } from '../../types/bindings'; +import FolderIcon from '../icons/FolderIcon'; + +type FileIconProps = { + file?: FilePath | null; + /** + * This is multiplier for calculating icon size + * default: `1` + */ + size?: number; +}; + +const FileIcon = ({ file, size = 1 }: FileIconProps) => { + return ( + + {file?.is_dir ? ( + + + + ) : file?.file?.has_thumbnail ? ( + <>{/* TODO */} + ) : ( + + + + + + + + {/* File Icon & Extension */} + + {file?.extension && + icons[file.extension] && + (() => { + const Icon = icons[file.extension]; + return ; + })()} + + {file?.extension} + + + + )} + + ); +}; + +export default FileIcon; diff --git a/apps/mobile/src/components/file/FileItem.tsx b/apps/mobile/src/components/file/FileItem.tsx index 629fc489e..5bc5d8a03 100644 --- a/apps/mobile/src/components/file/FileItem.tsx +++ b/apps/mobile/src/components/file/FileItem.tsx @@ -1,76 +1,48 @@ -import { FilePath } from '@sd/core'; +import { useNavigation } from '@react-navigation/native'; import React from 'react'; -import { Text, View } from 'react-native'; -import Svg, { Path } from 'react-native-svg'; +import { Pressable, Text, View } from 'react-native'; -import icons from '../../assets/icons/file'; import tw from '../../lib/tailwind'; -import FolderIcon from '../icons/Folder'; +import { SharedScreenProps } from '../../navigation/SharedScreens'; +import { useFileModalStore } from '../../stores/useModalStore'; +import { FilePath } from '../../types/bindings'; +import FileIcon from './FileIcon'; type FileItemProps = { file?: FilePath | null; }; // TODO: Menu for file actions (File details, Share etc.) -// TODO: Sheet Modal for file details const FileItem = ({ file }: FileItemProps) => { + const fileRef = useFileModalStore((state) => state.fileRef); + const setData = useFileModalStore((state) => state.setData); + + const navigation = useNavigation['navigation']>(); + + function handlePress() { + if (!file) return; + + if (file.is_dir) { + navigation.navigate('Location', { id: file.location_id }); + } else { + setData(file); + fileRef.current.present(); + } + } + return ( - - + + {/* Folder Icons/Thumbnail etc. */} - - {file?.is_dir ? ( - - - - ) : file?.file?.has_thumbnail ? ( - <>{/* TODO */} - ) : ( - - - - - - - - {/* File Icon & Extension */} - - {file?.extension && icons[file.extension] ? ( - (() => { - const Icon = icons[file.extension]; - return ; - })() - ) : ( - <> - )} - - {file?.extension} - - - - )} - - {/* Name */} + {file?.name} - + ); }; diff --git a/apps/mobile/src/components/header/Header.tsx b/apps/mobile/src/components/header/Header.tsx new file mode 100644 index 000000000..8838ade88 --- /dev/null +++ b/apps/mobile/src/components/header/Header.tsx @@ -0,0 +1,41 @@ +import { useDrawerStatus } from '@react-navigation/drawer'; +import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'; +import { useNavigation } from '@react-navigation/native'; +import { MotiView } from 'moti'; +import { List } from 'phosphor-react-native'; +import React from 'react'; +import { Pressable, Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import tw from '../../lib/tailwind'; + +const Header = () => { + const navigation = useNavigation(); + + const { top } = useSafeAreaInsets(); + + const isDrawerOpen = useDrawerStatus() === 'open'; + + return ( + + + navigation.openDrawer()}> + + + + + navigation.navigate('Search')} + > + Search + + + + ); +}; + +export default Header; diff --git a/apps/mobile/src/components/icons/Folder.tsx b/apps/mobile/src/components/icons/FolderIcon.tsx similarity index 81% rename from apps/mobile/src/components/icons/Folder.tsx rename to apps/mobile/src/components/icons/FolderIcon.tsx index 0fa11688f..311544248 100644 --- a/apps/mobile/src/components/icons/Folder.tsx +++ b/apps/mobile/src/components/icons/FolderIcon.tsx @@ -1,9 +1,8 @@ +import FolderWhite from '@sd/assets/svgs/folder-white.svg'; +import Folder from '@sd/assets/svgs/folder.svg'; import React from 'react'; import { SvgProps } from 'react-native-svg'; -import FolderWhite from '../../assets/temp/folder-white.svg'; -import Folder from '../../assets/temp/folder.svg'; - type FolderProps = { /** * Render a white folder icon diff --git a/apps/mobile/src/components/modals/FileDetails.tsx b/apps/mobile/src/components/modals/FileDetails.tsx deleted file mode 100644 index 1f4c77b84..000000000 --- a/apps/mobile/src/components/modals/FileDetails.tsx +++ /dev/null @@ -1 +0,0 @@ -// Only mount this modal if `FileItem` gets used in screen. diff --git a/apps/mobile/src/components/modals/FileModal.tsx b/apps/mobile/src/components/modals/FileModal.tsx new file mode 100644 index 000000000..e5658ee1d --- /dev/null +++ b/apps/mobile/src/components/modals/FileModal.tsx @@ -0,0 +1,128 @@ +import { BottomSheetModal, BottomSheetScrollView } from '@gorhom/bottom-sheet'; +import { format } from 'date-fns'; +import React, { useRef } from 'react'; +import { Button, Pressable, Text, View } from 'react-native'; +import { ChevronLeftIcon } from 'react-native-heroicons/outline'; + +import tw from '../../lib/tailwind'; +import { useFileModalStore } from '../../stores/useModalStore'; +import Divider from '../base/Divider'; +import FileIcon from '../file/FileIcon'; +import ModalBackdrop from './layout/ModalBackdrop'; +import ModalHandle from './layout/ModalHandle'; + +/* +https://github.com/software-mansion/react-native-reanimated/issues/3296 +https://github.com/gorhom/react-native-bottom-sheet/issues/925 +https://github.com/gorhom/react-native-bottom-sheet/issues/1036 + +Reanimated has a bug where it sometimes doesn't animate on mount (IOS only?), doing a console.log() seems to do a re-render and fix the issue. +We can't do this for production obvs but until then they might fix it so, let's not try weird hacks for now and live with the logs. +*/ + +interface MetaItemProps { + title: string; + value: string; +} + +function MetaItem({ title, value }: MetaItemProps) { + return ( + + {title} + {value} + + ); +} + +export const FileModal = () => { + const { fileRef, data } = useFileModalStore(); + + const fileDetailsRef = useRef(null); + + return ( + <> + console.log(from, to)} + > + {data && ( + + {/* File Icon / Name */} + + + {/* File Name, Details etc. */} + + {data?.name} + + 5 MB, + + {data?.extension.toUpperCase()}, + + 15 Aug + + fileDetailsRef.current.present()}> + More + + + + {/* Divider */} + + {/* Buttons */} + + - - {/* Stats */} - - - {/* Devices */} - index.toString()} - renderItem={({ item }) => ( - - )} - /> - - - + + + {/* Stats */} + + {/* Spacing */} + + {/* Devices */} + index.toString()} + renderItem={({ item }) => ( + + )} + /> + + ); } diff --git a/apps/mobile/src/screens/Photos.tsx b/apps/mobile/src/screens/Photos.tsx index ca3f4e7c6..5742265ea 100644 --- a/apps/mobile/src/screens/Photos.tsx +++ b/apps/mobile/src/screens/Photos.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { Text, View } from 'react-native'; import tw from '../lib/tailwind'; -import type { TabScreenProps } from '../navigation/TabNavigator'; +import { PhotosStackScreenProps } from '../navigation/tabs/PhotosStack'; -export default function PhotosScreen({ navigation }: TabScreenProps<'Photos'>) { +export default function PhotosScreen({ navigation }: PhotosStackScreenProps<'Photos'>) { return ( Photos diff --git a/apps/mobile/src/screens/Spaces.tsx b/apps/mobile/src/screens/Spaces.tsx index 4b3481acf..219041ddc 100644 --- a/apps/mobile/src/screens/Spaces.tsx +++ b/apps/mobile/src/screens/Spaces.tsx @@ -1,14 +1,13 @@ import React from 'react'; -import { Text } from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; +import { Text, View } from 'react-native'; import tw from '../lib/tailwind'; -import type { TabScreenProps } from '../navigation/TabNavigator'; +import { SpacesStackScreenProps } from '../navigation/tabs/SpacesStack'; -export default function SpacesScreen({ navigation }: TabScreenProps<'Spaces'>) { +export default function SpacesScreen({ navigation }: SpacesStackScreenProps<'Spaces'>) { return ( - + Spaces - + ); } diff --git a/apps/mobile/src/screens/Tag.tsx b/apps/mobile/src/screens/Tag.tsx index ea41fac20..7401157cc 100644 --- a/apps/mobile/src/screens/Tag.tsx +++ b/apps/mobile/src/screens/Tag.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { Text, View } from 'react-native'; import tw from '../lib/tailwind'; +import { SharedScreenProps } from '../navigation/SharedScreens'; -export default function TagScreen({ navigation, route }: any) { +export default function TagScreen({ navigation, route }: SharedScreenProps<'Tag'>) { const { id } = route.params; return ( diff --git a/apps/mobile/src/screens/modals/Search.tsx b/apps/mobile/src/screens/modals/Search.tsx new file mode 100644 index 000000000..2654cf46b --- /dev/null +++ b/apps/mobile/src/screens/modals/Search.tsx @@ -0,0 +1,57 @@ +import { MagnifyingGlass } from 'phosphor-react-native'; +import React, { useState } from 'react'; +import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { Button } from '../../components/base/Button'; +import tw from '../../lib/tailwind'; +import { RootStackScreenProps } from '../../navigation'; + +const SearchScreen = ({ navigation }: RootStackScreenProps<'Search'>) => { + const { top } = useSafeAreaInsets(); + + const [loading, setLoading] = useState(false); + + return ( + + {/* Header */} + + {/* Search Input */} + + + + {loading ? ( + + ) : ( + + )} + + + + + {/* Cancel Button */} + navigation.goBack()}> + Cancel + + + {/* Content */} + + + + + ); +}; + +export default SearchScreen; diff --git a/apps/mobile/src/screens/modals/settings/Settings.tsx b/apps/mobile/src/screens/modals/settings/Settings.tsx index e6e747bb3..73fd7d643 100644 --- a/apps/mobile/src/screens/modals/settings/Settings.tsx +++ b/apps/mobile/src/screens/modals/settings/Settings.tsx @@ -6,7 +6,7 @@ import { RootStackScreenProps } from '../../../navigation'; export default function SettingsScreen({ navigation }: RootStackScreenProps<'Settings'>) { return ( - + Settings diff --git a/apps/mobile/src/screens/onboarding/Onboarding.tsx b/apps/mobile/src/screens/onboarding/Onboarding.tsx index f656bb835..a56eed859 100644 --- a/apps/mobile/src/screens/onboarding/Onboarding.tsx +++ b/apps/mobile/src/screens/onboarding/Onboarding.tsx @@ -12,10 +12,6 @@ const OnboardingScreen = ({ navigation }: OnboardingStackScreenProps<'Onboarding const { hideOnboarding } = useOnboardingStore(); function onButtonPress() { - // Persist onboarding state only when app in production for now. - // if (process.env.NODE_ENV === 'production') { - // setItemToStorage('@onboarding', '1'); - // } setItemToStorage('@onboarding', '1'); // TODO: Add a loading indicator to button as this takes a second or so. hideOnboarding(); @@ -26,8 +22,7 @@ const OnboardingScreen = ({ navigation }: OnboardingStackScreenProps<'Onboarding {/* Logo */} - {/* TODO: Change this to @sd/assets. */} - + {/* Text */} diff --git a/apps/mobile/src/stores/useLibraryStore.ts b/apps/mobile/src/stores/useLibraryStore.ts new file mode 100644 index 000000000..77294c4cc --- /dev/null +++ b/apps/mobile/src/stores/useLibraryStore.ts @@ -0,0 +1,13 @@ +import create from 'zustand'; + +interface LibraryStore { + currentLibraryUuid: string | null; + switchLibrary: (id: string) => void; +} + +export const useLibraryStore = create()((set) => ({ + currentLibraryUuid: null, + switchLibrary: (uuid) => { + set((state) => ({ currentLibraryUuid: uuid })); + } +})); diff --git a/apps/mobile/src/stores/useModalStore.ts b/apps/mobile/src/stores/useModalStore.ts new file mode 100644 index 000000000..3c3c3f6bd --- /dev/null +++ b/apps/mobile/src/stores/useModalStore.ts @@ -0,0 +1,19 @@ +import { BottomSheetModal } from '@gorhom/bottom-sheet'; +import React from 'react'; +import create from 'zustand'; + +import { FilePath } from '../types/bindings'; + +interface FileModalState { + fileRef: React.RefObject; + data: FilePath | null; + setData: (data: FilePath) => void; + clearData: () => void; +} + +export const useFileModalStore = create((set) => ({ + fileRef: React.createRef(), + data: null, + setData: (data: FilePath) => set((_) => ({ data })), + clearData: () => set((_) => ({ data: null })) +})); diff --git a/apps/mobile/src/types/bindings.ts b/apps/mobile/src/types/bindings.ts new file mode 100644 index 000000000..858a453e3 --- /dev/null +++ b/apps/mobile/src/types/bindings.ts @@ -0,0 +1,117 @@ +// This file was generated by [rspc](https://github.com/oscartbeaumont/rspc). Do not edit this file manually. + +export type Operations = { + queries: + { key: ["files.readMetadata", LibraryArgs], result: null } | + { key: ["jobs.getHistory", LibraryArgs], result: Array } | + { key: ["jobs.getRunning", LibraryArgs], result: Array } | + { key: ["library.get"], result: Array } | + { key: ["locations.getExplorerDir", LibraryArgs], result: DirectoryWithContents } | + { key: ["locations.getById", LibraryArgs], result: Location | null } | + { key: ["getNode"], result: NodeState } | + { key: ["version"], result: string } | + { key: ["volumes.get"], result: Array } | + { key: ["tags.getFilesForTag", LibraryArgs], result: Tag | null } | + { key: ["tags.get", LibraryArgs], result: Array } | + { key: ["library.getStatistics", LibraryArgs], result: Statistics } | + { key: ["locations.get", LibraryArgs], result: Array }, + mutations: + { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | + { key: ["files.setNote", LibraryArgs], result: null } | + { key: ["library.create", string], result: null } | + { key: ["library.delete", string], result: null } | + { key: ["files.setFavorite", LibraryArgs], result: null } | + { key: ["locations.update", LibraryArgs], result: null } | + { key: ["tags.update", LibraryArgs], result: null } | + { key: ["tags.assign", LibraryArgs], result: null } | + { key: ["tags.delete", LibraryArgs], result: null } | + { key: ["library.edit", EditLibraryArgs], result: null } | + { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | + { key: ["locations.create", LibraryArgs], result: Location } | + { key: ["locations.quickRescan", LibraryArgs], result: null } | + { key: ["files.delete", LibraryArgs], result: null } | + { key: ["locations.delete", LibraryArgs], result: null } | + { key: ["locations.fullRescan", LibraryArgs], result: null } | + { key: ["tags.create", LibraryArgs], result: Tag }, + subscriptions: + { key: ["jobs.newThumbnail", LibraryArgs], result: string } | + { key: ["invalidateQuery"], result: InvalidateOperationEvent } +}; + +export interface GenerateThumbsForLocationArgs { id: number, path: string } + +export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } + +export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, date_created: string, node: Node | null | null, file_paths: Array | null } + +export interface TagAssignArgs { file_id: number, tag_id: number } + +export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } + +export interface DirectoryWithContents { directory: FilePath, contents: Array } + +export interface ConfigMetadata { version: string | null } + +export interface TagUpdateArgs { id: number, name: string | null, color: string | null } + +export interface LibraryConfig { version: string | null, name: string, description: string } + +export interface IdentifyUniqueFilesArgs { id: number, path: string } + +export interface FilePath { id: number, is_dir: boolean, location_id: number | null, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } + +export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } + +export interface SetFavoriteArgs { id: number, favorite: boolean } + +export interface LocationUpdateArgs { id: number, name: string | null } + +export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } + +export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } + +export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } + +export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } + +export interface TagCreateArgs { name: string, color: string } + +export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } + +export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } + +export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } + +export interface EditLibraryArgs { id: string, name: string | null, description: string | null } + +export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } + +export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } + +export interface GetExplorerDirArgs { location_id: number, path: string, limit: number } + +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" + +export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } + +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } + +export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } + +export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } + +export interface InvalidateOperationEvent { key: string, arg: any } + +export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } + +export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } + +export interface LibraryArgs { library_id: string, arg: T } + +export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } + +export interface File { id: number, cas_id: string, integrity_checksum: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } + +export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } + +export interface SetNoteArgs { id: number, note: string | null } diff --git a/apps/mobile/src/types/declarations.d.ts b/apps/mobile/src/types/declarations.d.ts index 793f67889..1eae33fe9 100644 --- a/apps/mobile/src/types/declarations.d.ts +++ b/apps/mobile/src/types/declarations.d.ts @@ -4,11 +4,3 @@ declare module '*.svg' { const content: React.FC; export default content; } - -// This declaration is used by useNavigation, Link, ref etc. -declare global { - namespace ReactNavigation { - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface RootParamList extends RootStackParamList {} - } -} diff --git a/apps/server/Cargo.toml b/apps/server/Cargo.toml index 2a512ff97..4a8dc55cf 100644 --- a/apps/server/Cargo.toml +++ b/apps/server/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] sdcore = { path = "../../core", features = [] } +rspc = { version = "0.0.4", features = ["axum"] } axum = "0.5.13" tokio = { version = "1.17.0", features = ["sync", "rt-multi-thread", "signal"] } tracing = "0.1.35" diff --git a/core/Cargo.toml b/core/Cargo.toml index f199eefdd..5a309048b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -6,10 +6,13 @@ authors = ["Spacedrive Technology Inc."] license = "GNU GENERAL PUBLIC LICENSE" repository = "https://github.com/spacedriveapp/spacedrive" edition = "2021" +rust-version = "1.63.0" [features] -p2p = [ -] # This feature controlls whether the Spacedrive Core contains the Peer to Peer syncing engine (It isn't required for the hosted core so we can disable it). +default = ["p2p"] +p2p = [] # This feature controlls whether the Spacedrive Core contains the Peer to Peer syncing engine (It isn't required for the hosted core so we can disable it). +mobile = [] # This feature allows features to be disabled when the Core is running on mobile. +ffmpeg = ["dep:ffmpeg-next"] # This feature controls whether the Spacedrive Core contains functionality which requires FFmpeg. [dependencies] hostname = "0.3.1" @@ -32,8 +35,6 @@ prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust "sqlite-create-many", ] } rspc = { version = "0.0.4", features = [ - "axum", - "tauri", "uuid", "chrono", "tracing", @@ -42,14 +43,13 @@ walkdir = "^2.3.2" uuid = { version = "1.1.2", features = ["v4", "serde"] } sysinfo = "0.23.9" thiserror = "1.0.30" -core-derive = { path = "./derive" } tokio = { version = "1.17.0", features = ["sync", "rt-multi-thread"] } include_dir = { version = "0.7.2", features = ["glob"] } async-trait = "^0.1.52" image = "0.24.1" webp = "0.2.2" -ffmpeg-next = "5.0.3" +ffmpeg-next = { version = "5.0.3", optional = true, features = [] } fs_extra = "1.2.0" tracing = "0.1.35" tracing-subscriber = { version = "0.3.14", features = ["env-filter"] } diff --git a/core/derive/Cargo.toml b/core/derive/Cargo.toml deleted file mode 100644 index dea7baf4a..000000000 --- a/core/derive/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "core-derive" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] -quote = "1.0.18" -syn = "1.0.91" \ No newline at end of file diff --git a/core/derive/src/lib.rs b/core/derive/src/lib.rs deleted file mode 100644 index 2d107dab2..000000000 --- a/core/derive/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput}; - -/// This macro must be executed in a file with `PropertyOperationCtx` defined and in the same package as the SyncContext is defined. -/// The creates: -/// ```rust -/// impl PropertyOperation { -/// fn apply(operation: PropertyOperationCtx, ctx: SyncContext) { -/// match operation.resource { -/// PropertyOperation::Tag(method) => method.apply(ctx), -/// }; -/// } -/// } -/// ``` -#[proc_macro_derive(PropertyOperationApply)] -pub fn property_operation_apply(input: TokenStream) -> TokenStream { - let DeriveInput { ident, data, .. } = parse_macro_input!(input); - - if let Data::Enum(data) = data { - let impls = data.variants.iter().map(|variant| { - let variant_ident = &variant.ident; - quote! { - #ident::#variant_ident(method) => method.apply(ctx), - } - }); - - let expanded = quote! { - impl #ident { - fn apply(operation: CrdtCtx, ctx: self::engine::SyncContext) { - match operation.resource { - #(#impls)* - }; - } - } - }; - - TokenStream::from(expanded) - } else { - panic!("The 'PropertyOperationApply' macro can only be used on enums!"); - } -} diff --git a/core/index.ts b/core/index.ts index c70c78aea..858a453e3 100644 --- a/core/index.ts +++ b/core/index.ts @@ -1,118 +1,117 @@ -/* eslint-disable */ // This file was generated by [rspc](https://github.com/oscartbeaumont/rspc). Do not edit this file manually. export type Operations = { queries: - { key: ["getNode"], result: NodeState } | - { key: ["version"], result: string } | - { key: ["tags.getFilesForTag", LibraryArgs], result: Tag | null } | - { key: ["locations.getById", LibraryArgs], result: Location | null } | { key: ["files.readMetadata", LibraryArgs], result: null } | - { key: ["locations.get", LibraryArgs], result: Array } | + { key: ["jobs.getHistory", LibraryArgs], result: Array } | { key: ["jobs.getRunning", LibraryArgs], result: Array } | { key: ["library.get"], result: Array } | { key: ["locations.getExplorerDir", LibraryArgs], result: DirectoryWithContents } | + { key: ["locations.getById", LibraryArgs], result: Location | null } | + { key: ["getNode"], result: NodeState } | + { key: ["version"], result: string } | + { key: ["volumes.get"], result: Array } | + { key: ["tags.getFilesForTag", LibraryArgs], result: Tag | null } | { key: ["tags.get", LibraryArgs], result: Array } | - { key: ["jobs.getHistory", LibraryArgs], result: Array } | { key: ["library.getStatistics", LibraryArgs], result: Statistics } | - { key: ["volumes.get"], result: Array }, + { key: ["locations.get", LibraryArgs], result: Array }, mutations: + { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | + { key: ["files.setNote", LibraryArgs], result: null } | + { key: ["library.create", string], result: null } | + { key: ["library.delete", string], result: null } | { key: ["files.setFavorite", LibraryArgs], result: null } | - { key: ["tags.assign", LibraryArgs], result: null } | { key: ["locations.update", LibraryArgs], result: null } | + { key: ["tags.update", LibraryArgs], result: null } | + { key: ["tags.assign", LibraryArgs], result: null } | + { key: ["tags.delete", LibraryArgs], result: null } | + { key: ["library.edit", EditLibraryArgs], result: null } | + { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | + { key: ["locations.create", LibraryArgs], result: Location } | + { key: ["locations.quickRescan", LibraryArgs], result: null } | + { key: ["files.delete", LibraryArgs], result: null } | { key: ["locations.delete", LibraryArgs], result: null } | { key: ["locations.fullRescan", LibraryArgs], result: null } | - { key: ["tags.delete", LibraryArgs], result: null } | - { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | - { key: ["library.delete", string], result: null } | - { key: ["files.delete", LibraryArgs], result: null } | - { key: ["locations.quickRescan", LibraryArgs], result: null } | - { key: ["locations.create", LibraryArgs], result: Location } | - { key: ["files.setNote", LibraryArgs], result: null } | - { key: ["library.edit", EditLibraryArgs], result: null } | - { key: ["tags.create", LibraryArgs], result: Tag } | - { key: ["library.create", string], result: null } | - { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | - { key: ["tags.update", LibraryArgs], result: null }, + { key: ["tags.create", LibraryArgs], result: Tag }, subscriptions: { key: ["jobs.newThumbnail", LibraryArgs], result: string } | { key: ["invalidateQuery"], result: InvalidateOperationEvent } }; -export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } +export interface GenerateThumbsForLocationArgs { id: number, path: string } -export interface SetFavoriteArgs { id: number, favorite: boolean } - -export interface LibraryConfig { version: string | null, name: string, description: string } - -export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } - -export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } - -export interface GetExplorerDirArgs { location_id: number, path: string, limit: number } - -export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } - -export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } - -export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } +export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, date_created: string, node: Node | null | null, file_paths: Array | null } -export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } +export interface TagAssignArgs { file_id: number, tag_id: number } -export interface InvalidateOperationEvent { key: string, arg: any } - -export interface LibraryArgs { library_id: string, arg: T } - -export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } - -export interface GenerateThumbsForLocationArgs { id: number, path: string } - -export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } +export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } export interface DirectoryWithContents { directory: FilePath, contents: Array } -export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } +export interface ConfigMetadata { version: string | null } -export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } +export interface TagUpdateArgs { id: number, name: string | null, color: string | null } -export interface File { id: number, cas_id: string, integrity_checksum: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } - -export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } - -export interface SetNoteArgs { id: number, note: string | null } +export interface LibraryConfig { version: string | null, name: string, description: string } export interface IdentifyUniqueFilesArgs { id: number, path: string } -export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } +export interface FilePath { id: number, is_dir: boolean, location_id: number | null, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } + +export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } + +export interface SetFavoriteArgs { id: number, favorite: boolean } export interface LocationUpdateArgs { id: number, name: string | null } export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } -export interface TagAssignArgs { file_id: number, tag_id: number } +export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } -export interface FilePath { id: number, is_dir: boolean, location_id: number | null, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } - -export interface TagCreateArgs { name: string, color: string } - -export interface EditLibraryArgs { id: string, name: string | null, description: string | null } - -export interface TagUpdateArgs { id: number, name: string | null, color: string | null } - -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" +export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } +export interface TagCreateArgs { name: string, color: string } + +export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } + +export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } + +export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } + +export interface EditLibraryArgs { id: string, name: string | null, description: string | null } + +export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } + export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } +export interface GetExplorerDirArgs { location_id: number, path: string, limit: number } + +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" + +export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } + +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } + export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } -export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } +export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } -export interface ConfigMetadata { version: string | null } +export interface InvalidateOperationEvent { key: string, arg: any } + +export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } + +export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } + +export interface LibraryArgs { library_id: string, arg: T } export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } -export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } +export interface File { id: number, cas_id: string, integrity_checksum: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } + +export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } + +export interface SetNoteArgs { id: number, note: string | null } diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs index ad1c649f1..0ffa98e5a 100644 --- a/core/src/api/mod.rs +++ b/core/src/api/mod.rs @@ -16,7 +16,7 @@ use crate::{ use utils::{InvalidRequests, InvalidateOperationEvent}; -pub(crate) type Router = rspc::Router; +pub type Router = rspc::Router; pub(crate) type RouterBuilder = rspc::RouterBuilder; /// Represents an internal core event, these are exposed to client via a rspc subscription. @@ -133,5 +133,9 @@ mod tests { let r = super::mount(); r.export_ts(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("./index.ts")) .expect("Error exporting rspc Typescript bindings!"); + r.export_ts( + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../apps/mobile/src/types/bindings.ts"), + ) + .expect("Error exporting rspc Typescript bindings!"); } } diff --git a/core/src/api/utils/invalidate.rs b/core/src/api/utils/invalidate.rs index 1c9d1e312..df397577d 100644 --- a/core/src/api/utils/invalidate.rs +++ b/core/src/api/utils/invalidate.rs @@ -1,5 +1,9 @@ -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +#[cfg(debug_assertions)] +use std::sync::Mutex; + +#[cfg(debug_assertions)] use once_cell::sync::OnceCell; use rspc::{internal::specta::DataType, Type}; use serde::Serialize; @@ -27,6 +31,7 @@ impl InvalidateOperationEvent { /// a request to invalidate a specific resource #[derive(Debug)] +#[allow(dead_code)] pub(crate) struct InvalidationRequest { pub key: &'static str, pub arg_ty: DataType, @@ -35,6 +40,7 @@ pub(crate) struct InvalidationRequest { /// invalidation request for a specific resource #[derive(Debug, Default)] +#[allow(dead_code)] pub(crate) struct InvalidRequests { pub queries: Vec, } diff --git a/core/src/encode/metadata.rs b/core/src/encode/metadata.rs index 9c86229c2..d22fd5317 100644 --- a/core/src/encode/metadata.rs +++ b/core/src/encode/metadata.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "ffmpeg")] use ffmpeg_next::format; #[derive(Default, Debug)] @@ -21,9 +22,10 @@ pub struct Stream { } #[derive(Debug)] +#[allow(dead_code)] // TODO: Remove this when we start using ffmpeg pub enum StreamKind { - // Video(VideoStream), - // Audio(AudioStream), + Video(VideoStream), + Audio(AudioStream), } #[derive(Debug)] @@ -31,6 +33,7 @@ pub struct VideoStream { pub width: u32, pub height: u32, pub aspect_ratio: String, + #[cfg(feature = "ffmpeg")] pub format: format::Pixel, pub bitrate: usize, } @@ -38,6 +41,7 @@ pub struct VideoStream { #[derive(Debug)] pub struct AudioStream { pub channels: u16, + #[cfg(feature = "ffmpeg")] pub format: format::Sample, pub bitrate: usize, pub rate: u32, diff --git a/core/src/lib.rs b/core/src/lib.rs index bef89167b..e69cd9b06 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,9 +9,7 @@ use tracing_subscriber::{filter::LevelFilter, fmt, prelude::*, EnvFilter}; use tokio::{fs, sync::broadcast}; -pub use rspc; // We expose rspc so we can access it in the Desktop app - -pub(crate) mod api; +pub mod api; pub(crate) mod encode; pub(crate) mod file; pub(crate) mod job; diff --git a/core/src/tag/mod.rs b/core/src/tag/mod.rs deleted file mode 100644 index 9cf480010..000000000 --- a/core/src/tag/mod.rs +++ /dev/null @@ -1,185 +0,0 @@ -use crate::{ - file::File, - library::LibraryContext, - prisma::{self, file, tag, tag_on_file}, - ClientQuery, CoreError, CoreEvent, CoreResponse, LibraryQuery, -}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; -use ts_rs::TS; -use uuid::Uuid; - -#[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export)] -pub struct Tag { - pub id: i32, - pub pub_id: Uuid, - pub name: Option, - pub color: Option, - - pub total_files: Option, - pub redundancy_goal: Option, - - pub date_created: chrono::DateTime, - pub date_modified: chrono::DateTime, -} - -#[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export)] -pub struct TagOnFile { - pub tag_id: i32, - pub tag: Option, - - pub file_id: i32, - pub file: Option, - - pub date_created: chrono::DateTime, -} - -impl From for Tag { - fn from(data: tag::Data) -> Self { - Self { - id: data.id, - pub_id: Uuid::from_slice(&data.pub_id).unwrap(), - name: data.name, - color: data.color, - total_files: data.total_files, - redundancy_goal: data.redundancy_goal, - date_created: data.date_created.into(), - date_modified: data.date_modified.into(), - } - } -} - -impl From for TagOnFile { - fn from(data: tag_on_file::Data) -> Self { - Self { - tag_id: data.tag_id, - tag: data.tag.map(|t| (*t).into()), - file_id: data.file_id, - file: data.file.map(|f| (*f).into()), - date_created: data.date_created.into(), - } - } -} - -#[derive(Serialize, Deserialize, TS, Debug)] -#[ts(export)] -pub struct TagWithFiles { - pub tag: Tag, - pub files_with_tag: Vec, -} - -#[derive(Error, Debug)] -pub enum TagError { - // #[error("Tag not found")] - // TagNotFound(i32), - #[error("Database error")] - DatabaseError(#[from] prisma::QueryError), -} - -pub async fn create_tag( - ctx: LibraryContext, - name: String, - color: String, -) -> Result { - let created_tag = ctx - .db - .tag() - .create( - tag::pub_id::set(Uuid::new_v4().as_bytes().to_vec()), - vec![tag::name::set(Some(name)), tag::color::set(Some(color))], - ) - .exec() - .await - .unwrap(); - - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id, - query: LibraryQuery::GetTags, - })) - .await; - - Ok(CoreResponse::TagCreateResponse(created_tag.into())) -} - -pub async fn update_tag( - ctx: LibraryContext, - id: i32, - name: Option, - color: Option, -) -> Result { - ctx.db - .tag() - .find_unique(tag::id::equals(id)) - .update(vec![tag::name::set(name), tag::color::set(color)]) - .exec() - .await - .unwrap(); - - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id, - query: LibraryQuery::GetTags, - })) - .await; - - Ok(CoreResponse::Success(())) -} - -pub async fn tag_assign( - ctx: LibraryContext, - file_id: i32, - tag_id: i32, -) -> Result { - ctx.db.tag_on_file().create( - tag_on_file::tag::link(tag::UniqueWhereParam::IdEquals(tag_id)), - tag_on_file::file::link(file::UniqueWhereParam::IdEquals(file_id)), - vec![], - ); - - Ok(CoreResponse::Success(())) -} - -pub async fn tag_delete(ctx: LibraryContext, id: i32) -> Result { - ctx.db - .tag() - .find_unique(tag::id::equals(id)) - .delete() - .exec() - .await? - .unwrap(); - - ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery { - library_id: ctx.id, - query: LibraryQuery::GetTags, - })) - .await; - - Ok(CoreResponse::Success(())) -} - -pub async fn get_files_for_tag(ctx: LibraryContext, id: i32) -> Result { - let tag: Option = ctx - .db - .tag() - .find_unique(tag::id::equals(id)) - .exec() - .await? - .map(Into::into); - - Ok(CoreResponse::GetTag(tag)) -} - -pub async fn get_all_tags(ctx: LibraryContext) -> Result { - let tags: Vec = ctx - .db - .tag() - .find_many(vec![]) - .exec() - .await? - .into_iter() - .map(Into::into) - .collect(); - - Ok(CoreResponse::GetTags(tags)) -} diff --git a/lefthook.yml b/lefthook.yml index 4ffa59ced..7a14eae9c 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -7,7 +7,7 @@ pre-push: parallel: true commands: type-check: - glob: "*.{ts,tsx}" + glob: '*.{ts,tsx}' run: pnpm typecheck lint: glob: '*.{ts,tsx}' @@ -22,7 +22,5 @@ pre-push: run: cargo clippy --package spacedrive -- -D warnings rust-lint-core: run: cargo clippy --package sdcore --lib -- -D warnings - rust-lint-core-derive: - run: cargo clippy --package core-derive --lib -- -D warnings rust-lint-server: run: cargo clippy --package server -- -D warnings diff --git a/package.json b/package.json index 2c1dbe5bb..e3406c04f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "0.0.0", "private": true, "scripts": { - "prep": "pnpm db:gen", + "prep": "pnpm db:gen && pnpm core codegen", + "build": "turbo run build", + "landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web", "db:migrate": "pnpm core prisma migrate dev", "db:gen": "pnpm core prisma generate", "format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"", diff --git a/apps/mobile/src/assets/temp/logo.png b/packages/assets/images/logo.png similarity index 100% rename from apps/mobile/src/assets/temp/logo.png rename to packages/assets/images/logo.png diff --git a/packages/assets/svgs/folder-white.svg b/packages/assets/svgs/folder-white.svg new file mode 100644 index 000000000..730602c10 --- /dev/null +++ b/packages/assets/svgs/folder-white.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/assets/svgs/folder.svg b/packages/assets/svgs/folder.svg new file mode 100644 index 000000000..96896b72f --- /dev/null +++ b/packages/assets/svgs/folder.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/client/package.json b/packages/client/package.json index d5c4dd2fd..fbdb41eed 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -18,8 +18,8 @@ }, "dependencies": { "@rspc/client": "^0.0.5", - "@sd/config": "workspace:*", "@sd/core": "workspace:*", + "@sd/config": "workspace:*", "@sd/interface": "workspace:*", "@tanstack/react-query": "^4.0.10", "eventemitter3": "^4.0.7", diff --git a/packages/interface/src/assets/svg/folder-white.svg b/packages/interface/src/assets/svg/folder-white.svg deleted file mode 100644 index 069ff8b01..000000000 --- a/packages/interface/src/assets/svg/folder-white.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/interface/src/assets/svg/folder.svg b/packages/interface/src/assets/svg/folder.svg deleted file mode 100644 index 5b9be3c5e..000000000 --- a/packages/interface/src/assets/svg/folder.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/packages/interface/src/components/file/FileItem.tsx b/packages/interface/src/components/file/FileItem.tsx index f402f42b9..8662c2ff6 100644 --- a/packages/interface/src/components/file/FileItem.tsx +++ b/packages/interface/src/components/file/FileItem.tsx @@ -1,10 +1,10 @@ +import { ReactComponent as Folder } from '@sd/assets/svgs/folder.svg'; import { LocationContext } from '@sd/client'; import { FilePath } from '@sd/core'; import clsx from 'clsx'; import React, { useContext } from 'react'; import icons from '../../assets/icons'; -import { ReactComponent as Folder } from '../../assets/svg/folder.svg'; import FileThumb from './FileThumb'; interface Props extends React.HTMLAttributes { diff --git a/packages/interface/src/components/icons/Folder.tsx b/packages/interface/src/components/icons/Folder.tsx index 41f6568b7..71b6b30fe 100644 --- a/packages/interface/src/components/icons/Folder.tsx +++ b/packages/interface/src/components/icons/Folder.tsx @@ -1,8 +1,7 @@ +import folderWhiteSvg from '@sd/assets/svgs/folder-white.svg'; +import folderSvg from '@sd/assets/svgs/folder.svg'; import React from 'react'; -import folderWhiteSvg from '../../assets/svg/folder-white.svg'; -import folderSvg from '../../assets/svg/folder.svg'; - interface FolderProps { /** * Append additional classes to the underlying SVG diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5adb32412510c4ec5efca44657dc5c2e76feadd..ae1e6663037add6fb16be299d44a7744840ecb18 100644 GIT binary patch delta 207 zcmcb$TVu~2jSXR3Y>CCisU^je7Yd0_-lQcwd7h=f`U zpsEv^oGT*&mA7i1$iICeKjRPm>1*s66{fe_Gb*>Av1bHgrtN3!nGah|H@(2AIeo(t zR^j&RmCQiQ0>rF9%m&2lK+LiIdL`$4!RZPC%pBAGrPxHamvwWRJA?I2UeKU8z5XmG j&vyRvoMLR#_gv!S+wORkvx;eY+g?uL?FX)NO1S|5M;lLk delta 160 zcmdn9N8{FRjSXR3lYd(ZPUaGpo_s<^97yv_p1@|knV&nHW%6km5vYJw^Lzg7@A(;j z=uhW#U{sv`)SgkPUCV(Hh?%x)IWQl#oLs{xIQ@hXqeZ(`6*CaC05K~NvjH(X5OZwT ys^Xj{xcx~tr@1p&@rtvYfs?mO@@=m_&&k0yo&N@>@b;dooXt$zWp8lGy8!@STsI#8 From 624a7c12c1f3c990e278513e748b0c5aec86d427 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Tue, 30 Aug 2022 10:50:08 +0800 Subject: [PATCH 51/51] remove phantom file + update rspc bindings A PR must have been merged incorrect introducing this file which is not used. --- apps/mobile/src/types/bindings.ts | 148 +++++++++++++++--------------- core/index.ts | 148 +++++++++++++++--------------- core/src/file/explorer/open.rs | 93 ------------------- 3 files changed, 148 insertions(+), 241 deletions(-) delete mode 100644 core/src/file/explorer/open.rs diff --git a/apps/mobile/src/types/bindings.ts b/apps/mobile/src/types/bindings.ts index 858a453e3..7114e01b6 100644 --- a/apps/mobile/src/types/bindings.ts +++ b/apps/mobile/src/types/bindings.ts @@ -2,116 +2,116 @@ export type Operations = { queries: - { key: ["files.readMetadata", LibraryArgs], result: null } | - { key: ["jobs.getHistory", LibraryArgs], result: Array } | + { key: ["library.getStatistics", LibraryArgs], result: Statistics } | { key: ["jobs.getRunning", LibraryArgs], result: Array } | - { key: ["library.get"], result: Array } | - { key: ["locations.getExplorerDir", LibraryArgs], result: DirectoryWithContents } | - { key: ["locations.getById", LibraryArgs], result: Location | null } | - { key: ["getNode"], result: NodeState } | { key: ["version"], result: string } | + { key: ["files.readMetadata", LibraryArgs], result: null } | + { key: ["locations.getExplorerDir", LibraryArgs], result: DirectoryWithContents } | + { key: ["jobs.getHistory", LibraryArgs], result: Array } | + { key: ["library.get"], result: Array } | { key: ["volumes.get"], result: Array } | { key: ["tags.getFilesForTag", LibraryArgs], result: Tag | null } | + { key: ["locations.get", LibraryArgs], result: Array } | + { key: ["locations.getById", LibraryArgs], result: Location | null } | { key: ["tags.get", LibraryArgs], result: Array } | - { key: ["library.getStatistics", LibraryArgs], result: Statistics } | - { key: ["locations.get", LibraryArgs], result: Array }, + { key: ["getNode"], result: NodeState }, mutations: + { key: ["tags.create", LibraryArgs], result: Tag } | + { key: ["files.setFavorite", LibraryArgs], result: null } | + { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | + { key: ["files.delete", LibraryArgs], result: null } | + { key: ["library.edit", EditLibraryArgs], result: null } | + { key: ["library.delete", string], result: null } | { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | { key: ["files.setNote", LibraryArgs], result: null } | { key: ["library.create", string], result: null } | - { key: ["library.delete", string], result: null } | - { key: ["files.setFavorite", LibraryArgs], result: null } | - { key: ["locations.update", LibraryArgs], result: null } | + { key: ["locations.quickRescan", LibraryArgs], result: null } | + { key: ["locations.delete", LibraryArgs], result: null } | { key: ["tags.update", LibraryArgs], result: null } | { key: ["tags.assign", LibraryArgs], result: null } | - { key: ["tags.delete", LibraryArgs], result: null } | - { key: ["library.edit", EditLibraryArgs], result: null } | - { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | { key: ["locations.create", LibraryArgs], result: Location } | - { key: ["locations.quickRescan", LibraryArgs], result: null } | - { key: ["files.delete", LibraryArgs], result: null } | - { key: ["locations.delete", LibraryArgs], result: null } | + { key: ["locations.update", LibraryArgs], result: null } | { key: ["locations.fullRescan", LibraryArgs], result: null } | - { key: ["tags.create", LibraryArgs], result: Tag }, + { key: ["tags.delete", LibraryArgs], result: null }, subscriptions: { key: ["jobs.newThumbnail", LibraryArgs], result: string } | { key: ["invalidateQuery"], result: InvalidateOperationEvent } }; -export interface GenerateThumbsForLocationArgs { id: number, path: string } - -export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } +export interface TagCreateArgs { name: string, color: string } export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, date_created: string, node: Node | null | null, file_paths: Array | null } -export interface TagAssignArgs { file_id: number, tag_id: number } - -export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } - -export interface DirectoryWithContents { directory: FilePath, contents: Array } - -export interface ConfigMetadata { version: string | null } - -export interface TagUpdateArgs { id: number, name: string | null, color: string | null } - -export interface LibraryConfig { version: string | null, name: string, description: string } - -export interface IdentifyUniqueFilesArgs { id: number, path: string } - -export interface FilePath { id: number, is_dir: boolean, location_id: number | null, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } - -export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } - -export interface SetFavoriteArgs { id: number, favorite: boolean } - -export interface LocationUpdateArgs { id: number, name: string | null } - -export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } - -export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } - -export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } - -export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } - -export interface TagCreateArgs { name: string, color: string } - -export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } - -export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } - -export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } +export interface File { id: number, cas_id: string, integrity_checksum: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } export interface EditLibraryArgs { id: string, name: string | null, description: string | null } +export interface LibraryArgs { library_id: string, arg: T } + +export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } + +export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } + export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } -export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } +export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } -export interface GetExplorerDirArgs { location_id: number, path: string, limit: number } - -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" +export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } -export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } +export interface FilePath { id: number, is_dir: boolean, location_id: number | null, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } -export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } +export interface LocationUpdateArgs { id: number, name: string | null } -export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } - -export interface InvalidateOperationEvent { key: string, arg: any } - -export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } - -export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } - -export interface LibraryArgs { library_id: string, arg: T } +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } -export interface File { id: number, cas_id: string, integrity_checksum: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } +export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } + +export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } + +export interface SetFavoriteArgs { id: number, favorite: boolean } + +export interface InvalidateOperationEvent { key: string, arg: any } + +export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } + +export interface ConfigMetadata { version: string | null } export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } +export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } + +export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } + +export interface LibraryConfig { version: string | null, name: string, description: string } + +export interface TagUpdateArgs { id: number, name: string | null, color: string | null } + +export interface TagAssignArgs { file_id: number, tag_id: number } + +export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } + +export interface GenerateThumbsForLocationArgs { id: number, path: string } + export interface SetNoteArgs { id: number, note: string | null } + +export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } + +export interface GetExplorerDirArgs { location_id: number, path: string, limit: number } + +export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } + +export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } + +export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } + +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } + +export interface IdentifyUniqueFilesArgs { id: number, path: string } + +export interface DirectoryWithContents { directory: FilePath, contents: Array } + +export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } diff --git a/core/index.ts b/core/index.ts index 858a453e3..7114e01b6 100644 --- a/core/index.ts +++ b/core/index.ts @@ -2,116 +2,116 @@ export type Operations = { queries: - { key: ["files.readMetadata", LibraryArgs], result: null } | - { key: ["jobs.getHistory", LibraryArgs], result: Array } | + { key: ["library.getStatistics", LibraryArgs], result: Statistics } | { key: ["jobs.getRunning", LibraryArgs], result: Array } | - { key: ["library.get"], result: Array } | - { key: ["locations.getExplorerDir", LibraryArgs], result: DirectoryWithContents } | - { key: ["locations.getById", LibraryArgs], result: Location | null } | - { key: ["getNode"], result: NodeState } | { key: ["version"], result: string } | + { key: ["files.readMetadata", LibraryArgs], result: null } | + { key: ["locations.getExplorerDir", LibraryArgs], result: DirectoryWithContents } | + { key: ["jobs.getHistory", LibraryArgs], result: Array } | + { key: ["library.get"], result: Array } | { key: ["volumes.get"], result: Array } | { key: ["tags.getFilesForTag", LibraryArgs], result: Tag | null } | + { key: ["locations.get", LibraryArgs], result: Array } | + { key: ["locations.getById", LibraryArgs], result: Location | null } | { key: ["tags.get", LibraryArgs], result: Array } | - { key: ["library.getStatistics", LibraryArgs], result: Statistics } | - { key: ["locations.get", LibraryArgs], result: Array }, + { key: ["getNode"], result: NodeState }, mutations: + { key: ["tags.create", LibraryArgs], result: Tag } | + { key: ["files.setFavorite", LibraryArgs], result: null } | + { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | + { key: ["files.delete", LibraryArgs], result: null } | + { key: ["library.edit", EditLibraryArgs], result: null } | + { key: ["library.delete", string], result: null } | { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | { key: ["files.setNote", LibraryArgs], result: null } | { key: ["library.create", string], result: null } | - { key: ["library.delete", string], result: null } | - { key: ["files.setFavorite", LibraryArgs], result: null } | - { key: ["locations.update", LibraryArgs], result: null } | + { key: ["locations.quickRescan", LibraryArgs], result: null } | + { key: ["locations.delete", LibraryArgs], result: null } | { key: ["tags.update", LibraryArgs], result: null } | { key: ["tags.assign", LibraryArgs], result: null } | - { key: ["tags.delete", LibraryArgs], result: null } | - { key: ["library.edit", EditLibraryArgs], result: null } | - { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | { key: ["locations.create", LibraryArgs], result: Location } | - { key: ["locations.quickRescan", LibraryArgs], result: null } | - { key: ["files.delete", LibraryArgs], result: null } | - { key: ["locations.delete", LibraryArgs], result: null } | + { key: ["locations.update", LibraryArgs], result: null } | { key: ["locations.fullRescan", LibraryArgs], result: null } | - { key: ["tags.create", LibraryArgs], result: Tag }, + { key: ["tags.delete", LibraryArgs], result: null }, subscriptions: { key: ["jobs.newThumbnail", LibraryArgs], result: string } | { key: ["invalidateQuery"], result: InvalidateOperationEvent } }; -export interface GenerateThumbsForLocationArgs { id: number, path: string } - -export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } +export interface TagCreateArgs { name: string, color: string } export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, date_created: string, node: Node | null | null, file_paths: Array | null } -export interface TagAssignArgs { file_id: number, tag_id: number } - -export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } - -export interface DirectoryWithContents { directory: FilePath, contents: Array } - -export interface ConfigMetadata { version: string | null } - -export interface TagUpdateArgs { id: number, name: string | null, color: string | null } - -export interface LibraryConfig { version: string | null, name: string, description: string } - -export interface IdentifyUniqueFilesArgs { id: number, path: string } - -export interface FilePath { id: number, is_dir: boolean, location_id: number | null, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } - -export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } - -export interface SetFavoriteArgs { id: number, favorite: boolean } - -export interface LocationUpdateArgs { id: number, name: string | null } - -export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } - -export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } - -export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } - -export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } - -export interface TagCreateArgs { name: string, color: string } - -export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } - -export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } - -export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } +export interface File { id: number, cas_id: string, integrity_checksum: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } export interface EditLibraryArgs { id: string, name: string | null, description: string | null } +export interface LibraryArgs { library_id: string, arg: T } + +export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } + +export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } + export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } -export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } +export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } -export interface GetExplorerDirArgs { location_id: number, path: string, limit: number } - -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" +export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } -export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } +export interface FilePath { id: number, is_dir: boolean, location_id: number | null, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } -export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } +export interface LocationUpdateArgs { id: number, name: string | null } -export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } - -export interface InvalidateOperationEvent { key: string, arg: any } - -export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } - -export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } - -export interface LibraryArgs { library_id: string, arg: T } +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } -export interface File { id: number, cas_id: string, integrity_checksum: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } +export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } + +export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } + +export interface SetFavoriteArgs { id: number, favorite: boolean } + +export interface InvalidateOperationEvent { key: string, arg: any } + +export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } + +export interface ConfigMetadata { version: string | null } export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } +export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } + +export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } + +export interface LibraryConfig { version: string | null, name: string, description: string } + +export interface TagUpdateArgs { id: number, name: string | null, color: string | null } + +export interface TagAssignArgs { file_id: number, tag_id: number } + +export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } + +export interface GenerateThumbsForLocationArgs { id: number, path: string } + export interface SetNoteArgs { id: number, note: string | null } + +export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } + +export interface GetExplorerDirArgs { location_id: number, path: string, limit: number } + +export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } + +export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } + +export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } + +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } + +export interface IdentifyUniqueFilesArgs { id: number, path: string } + +export interface DirectoryWithContents { directory: FilePath, contents: Array } + +export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } diff --git a/core/src/file/explorer/open.rs b/core/src/file/explorer/open.rs deleted file mode 100644 index 330ba151a..000000000 --- a/core/src/file/explorer/open.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::{ - encode::THUMBNAIL_CACHE_DIR_NAME, - file::{DirectoryWithContents, FileError, FilePath}, - library::LibraryContext, - prisma::file_path, - sys::get_location, -}; -use log::info; -use std::path::Path; - -pub async fn open_dir( - ctx: &LibraryContext, - location_id: i32, - path: impl AsRef, -) -> Result { - // get location - let location = get_location(ctx, location_id).await?; - - let path_str = path.as_ref().to_string_lossy().to_string(); - - let directory = ctx - .db - .file_path() - .find_first(vec![ - file_path::location_id::equals(Some(location.id)), - file_path::materialized_path::equals(path_str), - file_path::is_dir::equals(true), - ]) - .exec() - .await? - .ok_or_else(|| FileError::DirectoryNotFound(path.as_ref().to_path_buf()))?; - - info!("DIRECTORY: {:?}", directory); - - let mut file_paths: Vec = ctx - .db - .file_path() - .find_many(vec![ - file_path::location_id::equals(Some(location.id)), - file_path::parent_id::equals(Some(directory.id)), - ]) - .with(file_path::file::fetch()) - .exec() - .await? - .into_iter() - .map(Into::into) - .collect(); - - for file_path in &mut file_paths { - if let Some(file) = &mut file_path.file { - let thumb_path = ctx - .config() - .data_directory() - .join(THUMBNAIL_CACHE_DIR_NAME) - .join(location.id.to_string()) - .join(&file.cas_id) - .with_extension("webp"); - - file.has_thumbnail = thumb_path.exists(); - } - } - - Ok(DirectoryWithContents { - directory: directory.into(), - contents: file_paths, - }) -} - -// pub async fn open_tag(ctx: &LibraryContext, tag_id: i32) -> Result { -// let tag: Tag = ctx -// .db -// .tag() -// .find_unique(tag::id::equals(tag_id)) -// .exec() -// .await? -// .ok_or(TagError::TagNotFound(tag_id))? -// .into(); -// -// let files_with_tag: Vec = ctx -// .db -// .tag_on_file() -// .find_many(vec![tag_on_file::tag_id::equals(tag_id)]) -// .exec() -// .await? -// .into_iter() -// .map(Into::into) -// .collect(); -// -// Ok(TagWithFiles { -// tag, -// files_with_tag, -// }) -// }