A local-first private AI assistant for everyday use. Runs on-device models with encrypted P2P sync, and supports sharing chats publicly on ATProto.
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #83 from tilesprivacy/fix/mem-model-compatibility

open responses and mem model compatibility + fixes

authored by

Anandu Pavanan and committed by
GitHub
138f1dfb ef02a602

+219 -185
+2 -1
Cargo.lock
··· 3311 3311 3312 3312 [[package]] 3313 3313 name = "tiles" 3314 - version = "0.4.0-rc.1" 3314 + version = "0.4.0" 3315 3315 dependencies = [ 3316 3316 "anyhow", 3317 3317 "clap", ··· 3322 3322 "rustyline", 3323 3323 "serde", 3324 3324 "serde_json", 3325 + "tempfile", 3325 3326 "tilekit", 3326 3327 "tokio", 3327 3328 ]
+1 -1
scripts/install.sh
··· 4 4 ENV="prod" # prod is another env, try taking it from github env 5 5 REPO="tilesprivacy/tiles" 6 6 # VERSION="${TILES_VERSION:-latest}" 7 - VERSION="0.4.0-rc.1" 7 + VERSION="0.4.0" 8 8 INSTALL_DIR="$HOME/.local/bin" # CLI install location 9 9 SERVER_DIR="$HOME/.local/lib/tiles/server" # Python server folder 10 10 MODELFILE_DIR="$HOME/.local/lib/tiles/modelfiles" # Python server folder
+2 -2
server/stack/requirements/app-server/packages-app-server.txt
··· 17 17 markupsafe==3.0.3 18 18 mlx-lm==0.28.3 19 19 mypy-extensions==1.1.0 20 - numpy==2.4.1 20 + numpy==2.4.2 21 21 packaging==26.0 22 22 pathspec==1.0.4 23 23 platformdirs==4.5.1 ··· 31 31 safetensors==0.7.0 32 32 starlette==0.48.0 33 33 tokenizers==0.22.2 34 - tqdm==4.67.1 34 + tqdm==4.67.3 35 35 transformers==4.57.6 36 36 typing-extensions==4.15.0 37 37 typing-inspection==0.4.2
+2 -2
server/stack/requirements/app-server/pylock.app-server.meta.json
··· 1 1 { 2 2 "lock_input_hash": "sha256:182c606e20dd957344cc3adc54391f47f4b6dd80b4481ddf219392a7aad6e0ce", 3 3 "lock_version": 1, 4 - "locked_at": "2026-01-30T08:41:45.203370+00:00", 4 + "locked_at": "2026-02-04T07:05:20.498851+00:00", 5 5 "other_inputs_hash": "sha256:63b3c2cfe2ec414938e81dace7aac779c7b902bae681618cd8827e9f16880985", 6 - "requirements_hash": "sha256:288220847007f2f14c9a0aa2a972b33e92f6bb84f25dac1a248fbe6e55ec2bea", 6 + "requirements_hash": "sha256:fa370cac93a65e09d769497457de2c60027eb53d6b7f05e44ff23e5a4a7f1b2e", 7 7 "version_inputs_hash": "sha256:58db986b7cd72eeded675f7c9afd8138fe024fb51451131b5562922bbde3cf43" 8 8 }
+38 -38
server/stack/requirements/app-server/pylock.app-server.toml
··· 358 358 359 359 [[packages]] 360 360 name = "numpy" 361 - version = "2.4.1" 361 + version = "2.4.2" 362 362 index = "https://pypi.org/simple" 363 363 364 364 [[packages.wheels]] 365 - url = "https://files.pythonhosted.org/packages/20/ca/857722353421a27f1465652b2c66813eeeccea9d76d5f7b74b99f298e60e/numpy-2.4.1-cp313-cp313-macosx_11_0_arm64.whl" 366 - upload-time = 2026-01-10T06:43:09Z 367 - size = 12368657 365 + url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl" 366 + upload-time = 2026-01-31T23:11:19Z 367 + size = 14688322 368 368 369 369 [packages.wheels.hashes] 370 - sha256 = "82c55962006156aeef1629b953fd359064aa47e4d82cfc8e67f0918f7da3344f" 370 + sha256 = "bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979" 371 371 372 372 [[packages.wheels]] 373 - url = "https://files.pythonhosted.org/packages/81/0d/2377c917513449cc6240031a79d30eb9a163d32a91e79e0da47c43f2c0c8/numpy-2.4.1-cp313-cp313-macosx_14_0_arm64.whl" 374 - upload-time = 2026-01-10T06:43:13Z 375 - size = 5197256 373 + url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl" 374 + upload-time = 2026-01-31T23:11:22Z 375 + size = 5198157 376 376 377 377 [packages.wheels.hashes] 378 - sha256 = "71abbea030f2cfc3092a0ff9f8c8fdefdc5e0bf7d9d9c99663538bb0ecdac0b9" 378 + sha256 = "8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98" 379 379 380 380 [[packages.wheels]] 381 - url = "https://files.pythonhosted.org/packages/ba/87/d341e519956273b39d8d47969dd1eaa1af740615394fe67d06f1efa68773/numpy-2.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" 382 - upload-time = 2026-01-10T06:43:19Z 383 - size = 16359305 381 + url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" 382 + upload-time = 2026-01-31T23:11:28Z 383 + size = 16607311 384 384 385 385 [packages.wheels.hashes] 386 - sha256 = "d3e3087f53e2b4428766b54932644d148613c5a595150533ae7f00dab2f319a8" 386 + sha256 = "d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499" 387 387 388 388 [[packages.wheels]] 389 - url = "https://files.pythonhosted.org/packages/cf/b8/090b8bd27b82a844bb22ff8fdf7935cb1980b48d6e439ae116f53cdc2143/numpy-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl" 390 - upload-time = 2026-01-10T06:43:23Z 391 - size = 18284380 389 + url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl" 390 + upload-time = 2026-01-31T23:11:33Z 391 + size = 18334210 392 392 393 393 [packages.wheels.hashes] 394 - sha256 = "79e9e06c4c2379db47f3f6fc7a8652e7498251789bf8ff5bd43bf478ef314ca2" 394 + sha256 = "c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7" 395 395 396 396 [[packages.wheels]] 397 - url = "https://files.pythonhosted.org/packages/da/a1/354583ac5c4caa566de6ddfbc42744409b515039e085fab6e0ff942e0df5/numpy-2.4.1-cp313-cp313t-macosx_11_0_arm64.whl" 398 - upload-time = 2026-01-10T06:43:34Z 399 - size = 12496156 397 + url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl" 398 + upload-time = 2026-01-31T23:11:42Z 399 + size = 14815866 400 400 401 401 [packages.wheels.hashes] 402 - sha256 = "f93bc6892fe7b0663e5ffa83b61aab510aacffd58c16e012bb9352d489d90cb7" 402 + sha256 = "5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262" 403 403 404 404 [[packages.wheels]] 405 - url = "https://files.pythonhosted.org/packages/51/b0/42807c6e8cce58c00127b1dc24d365305189991f2a7917aa694a109c8d7d/numpy-2.4.1-cp313-cp313t-macosx_14_0_arm64.whl" 406 - upload-time = 2026-01-10T06:43:36Z 407 - size = 5324663 405 + url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl" 406 + upload-time = 2026-01-31T23:11:44Z 407 + size = 5325631 408 408 409 409 [packages.wheels.hashes] 410 - sha256 = "178de8f87948163d98a4c9ab5bee4ce6519ca918926ec8df195af582de28544d" 410 + sha256 = "d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913" 411 411 412 412 [[packages.wheels]] 413 - url = "https://files.pythonhosted.org/packages/03/d1/8cf62d8bb2062da4fb82dd5d49e47c923f9c0738032f054e0a75342faba7/numpy-2.4.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" 414 - upload-time = 2026-01-10T06:43:41Z 415 - size = 16407279 413 + url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" 414 + upload-time = 2026-01-31T23:11:50Z 415 + size = 16655398 416 416 417 417 [packages.wheels.hashes] 418 - sha256 = "529050522e983e00a6c1c6b67411083630de8b57f65e853d7b03d9281b8694d2" 418 + sha256 = "2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f" 419 419 420 420 [[packages.wheels]] 421 - url = "https://files.pythonhosted.org/packages/30/b4/e7f5ff8697274c9d0fa82398b6a372a27e5cef069b37df6355ccb1f1db1a/numpy-2.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl" 422 - upload-time = 2026-01-10T06:43:46Z 423 - size = 18329884 421 + url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl" 422 + upload-time = 2026-01-31T23:11:55Z 423 + size = 18379680 424 424 425 425 [packages.wheels.hashes] 426 - sha256 = "9171a42fcad32dcf3fa86f0a4faa5e9f8facefdb276f54b8b390d90447cff4e2" 426 + sha256 = "0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257" 427 427 428 428 [[packages]] 429 429 name = "packaging" ··· 756 756 757 757 [[packages]] 758 758 name = "tqdm" 759 - version = "4.67.1" 759 + version = "4.67.3" 760 760 index = "https://pypi.org/simple" 761 761 762 762 [[packages.wheels]] 763 - url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl" 764 - upload-time = 2024-11-24T20:12:19Z 765 - size = 78540 763 + url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl" 764 + upload-time = 2026-02-03T17:35:50Z 765 + size = 78374 766 766 767 767 [packages.wheels.hashes] 768 - sha256 = "26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2" 768 + sha256 = "ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf" 769 769 770 770 [[packages]] 771 771 name = "transformers"
+4 -1
tiles/Cargo.toml
··· 1 1 [package] 2 2 name = "tiles" 3 - version = "0.4.0-rc.1" 3 + version = "0.4.0" 4 4 edition = "2024" 5 5 6 6 [dependencies] ··· 15 15 futures-util = "0.3" 16 16 hf-hub = {version = "0.4", features = ["tokio"]} 17 17 rustyline = "17.0" 18 + 19 + [dev-dependencies] 20 + tempfile = "3"
+1 -1
tiles/src/main.rs
··· 5 5 mod commands; 6 6 #[derive(Debug, Parser)] 7 7 #[command(name = "tiles")] 8 - #[command(version, about = "Your private AI assistant with offline memory.", long_about = None)] 8 + #[command(version, about = "Your private and secure AI assistant for everyday use.", long_about = None, after_help = "Documentation: https://tiles.run/book\nReport issues: https://github.com/tilesprivacy/tiles/issues")] 9 9 struct Cli { 10 10 #[command(subcommand)] 11 11 command: Commands,
+29 -78
tiles/src/runtime/mlx.rs
··· 1 1 use crate::runtime::RunArgs; 2 2 use crate::utils::config::{ 3 - create_default_memory_folder, get_config_dir, get_default_memory_path, get_lib_dir, 3 + ConfigProvider, DefaultProvider, create_default_memory_folder, get_default_memory_path, 4 4 get_memory_path, set_memory_path, 5 5 }; 6 6 use crate::utils::hf_model_downloader::*; ··· 100 100 return Ok(()); 101 101 } 102 102 103 - let config_dir = get_config_dir()?; 104 - let mut server_dir = get_lib_dir()?; 103 + let config_dir = DefaultProvider.get_config_dir()?; 104 + let mut server_dir = DefaultProvider.get_lib_dir()?; 105 105 let pid_file = config_dir.join("server.pid"); 106 106 fs::create_dir_all(&config_dir).context("Failed to create config directory")?; 107 107 server_dir = server_dir.join("server"); ··· 129 129 println!("Server is not running"); 130 130 return Ok(()); 131 131 } 132 - let pid_file = get_config_dir()?.join("server.pid"); 132 + let pid_file = DefaultProvider.get_config_dir()?.join("server.pid"); 133 133 134 134 if !pid_file.exists() { 135 135 eprintln!("server pid doesnt exist"); ··· 144 144 } 145 145 } 146 146 147 - #[allow(dead_code)] 148 - fn run_model_by_sub_process(modelfile: Modelfile) { 149 - // build the arg list from modelfile 150 - let mut args: Vec<String> = vec![]; 151 - args.push("--model".to_owned()); 152 - args.push(modelfile.from.unwrap()); 153 - for parameter in modelfile.parameters { 154 - let param_value = parameter.value.to_string(); 155 - match parameter.param_type.as_str() { 156 - "num_predict" => { 157 - args.push("--max-tokens".to_owned()); 158 - args.push(param_value); 159 - } 160 - "temperature" => { 161 - args.push("--temp".to_owned()); 162 - args.push(param_value); 163 - } 164 - "top_p" => { 165 - args.push("--top-p".to_owned()); 166 - args.push(param_value); 167 - } 168 - "seed" => { 169 - args.push("--seed".to_owned()); 170 - args.push(param_value); 171 - } 172 - _ => {} 173 - } 174 - } 175 - if let Some(system_prompt) = modelfile.system { 176 - args.push("--system-prompt".to_owned()); 177 - args.push(system_prompt); 178 - } 179 - if let Some(adapter_path) = modelfile.adapter { 180 - args.push("--adapter-path".to_owned()); 181 - args.push(adapter_path); 182 - } 183 - let mut mlx = match Command::new("mlx_lm.chat").args(args).spawn() { 184 - Ok(child) => child, 185 - Err(e) => { 186 - if e.kind() == std::io::ErrorKind::NotFound { 187 - eprintln!("❌ Error: mlx_lm.chat command not found"); 188 - eprintln!("💡 Hint: Install mlx-lm by running: pip install mlx-lm"); 189 - eprintln!("📝 Note: mlx-lm is only available on macOS with Apple Silicon"); 190 - std::process::exit(1); 191 - } else { 192 - eprintln!("❌ Error: Failed to spawn mlx_lm.chat: {}", e); 193 - std::process::exit(1); 194 - } 195 - } 196 - }; 197 - 198 - if let Err(err) = mlx.wait() { 199 - eprintln!("❌ Error: Failed to wait for mlx_lm: {}", err); 200 - } 201 - } 202 - 203 147 struct TilesHinter; 204 148 205 149 impl Hinter for TilesHinter { ··· 207 151 208 152 fn hint(&self, line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<Self::Hint> { 209 153 if line.is_empty() { 210 - Some("Send a message (/? for help)".to_string()) 154 + Some("Send a message (/help for help)".to_string()) 211 155 } else { 212 156 None 213 157 } ··· 260 204 } 261 205 262 206 fn show_help(model_name: &str) { 263 - println!("\n=== Tiles REPL Help ===\n"); 207 + println!("\n=== Tiles REPL ===\n"); 208 + 209 + println!("Model:"); 210 + println!(" {}", model_name); 211 + 212 + println!("Directory:"); 213 + println!(" {}\n", get_memory_path().unwrap()); 264 214 265 215 println!("Available Commands:"); 266 - println!(" /? Show this help message"); 267 216 println!(" /help Show this help message"); 268 217 println!(" /bye Exit the REPL"); 269 218 println!(); 270 219 271 - println!("Current Model:"); 272 - println!(" {}", model_name); 273 - println!(); 220 + println!("\nDocumentation: https://tiles.run/book"); 274 221 275 - println!("Usage Tips:"); 276 - println!(" - Type your questions or prompts directly"); 222 + println!("Report issues: https://github.com/tilesprivacy/tiles/issues"); 277 223 println!(); 278 224 } 279 225 ··· 542 488 "python_code": python_code, 543 489 "messages": [{"role": "assistant", "content": g_reply}, {"role": "user", "content": input}] 544 490 }); 545 - let res = client 546 - .post("http://127.0.0.1:6969/v1/responses") 547 - .json(&body) 548 - .send() 549 - .await 550 - .unwrap(); 491 + let api_url = if run_args.memory { 492 + "http://127.0.0.1:6969/v1/chat/completions" 493 + } else { 494 + "http://127.0.0.1:6969/v1/responses" 495 + }; 496 + let res = client.post(api_url).json(&body).send().await.unwrap(); 551 497 552 498 let mut stream = res.bytes_stream(); 553 499 let mut accumulated = String::new(); ··· 574 520 575 521 // Parse JSON 576 522 let v: Value = serde_json::from_str(data).unwrap(); 577 - // println!("{:?}", v); 578 523 // Check for metrics in the response 579 524 if let Some(metrics_obj) = v.get("metrics") { 580 525 metrics = serde_json::from_value(metrics_obj.clone()).ok(); 581 526 } 582 - if let Some(delta) = v["output"][0]["content"][0]["text"].as_str() { 527 + let model_text: Option<&str> = if run_args.memory { 528 + v["choices"][0]["delta"]["content"].as_str() 529 + } else { 530 + v["output"][0]["content"][0]["text"].as_str() 531 + }; 532 + 533 + if let Some(delta) = model_text { 583 534 accumulated.push_str(delta); 584 535 if !run_args.memory && delta.contains("**[Answer]**") { 585 536 is_answer_start = true; ··· 648 599 649 600 fn get_default_modelfile(memory_mode: bool) -> Result<PathBuf> { 650 601 if memory_mode { 651 - let path = get_lib_dir()?.join("modelfiles/mem-agent"); 602 + let path = DefaultProvider.get_lib_dir()?.join("modelfiles/mem-agent"); 652 603 Ok(path) 653 604 } else { 654 - let path = get_lib_dir()?.join("modelfiles/gpt-oss"); 605 + let path = DefaultProvider.get_lib_dir()?.join("modelfiles/gpt-oss"); 655 606 Ok(path) 656 607 } 657 608 }
+75 -61
tiles/src/utils/config.rs
··· 5 5 use std::str::FromStr; 6 6 use std::{env, fs}; 7 7 8 - pub fn set_memory_path(path: &str) -> Result<String> { 9 - let path_buf = PathBuf::from_str(path)?; 10 - if path_buf.try_exists()? { 11 - let tiles_config_dir = get_config_dir()?; 12 - let tiles_config_memory_path = tiles_config_dir.join(".memory_path"); 13 - if tiles_config_memory_path.try_exists()? { 14 - fs::write(tiles_config_memory_path, path)?; 8 + pub trait ConfigProvider { 9 + fn get_config_dir(&self) -> Result<PathBuf>; 10 + fn get_data_dir(&self) -> Result<PathBuf>; 11 + fn get_lib_dir(&self) -> Result<PathBuf>; 12 + } 13 + 14 + #[derive(Debug, Default)] 15 + pub struct DefaultProvider; 16 + 17 + impl ConfigProvider for DefaultProvider { 18 + fn get_config_dir(&self) -> Result<PathBuf> { 19 + if cfg!(debug_assertions) { 20 + let base_dir = env::current_dir().context("Failed to fetch CURRENT_DIR")?; 21 + Ok(base_dir.join(".tiles_dev/tiles")) 15 22 } else { 16 - fs::create_dir_all(&tiles_config_dir) 17 - .context("Failed to create tiles config directory")?; 18 - fs::write(tiles_config_memory_path, path) 19 - .context("Failed to write the memory path to .memory_path")?; 23 + let home_dir = env::home_dir().context("Failed to fetch $HOME")?; 24 + let config_dir = match env::var("XDG_CONFIG_HOME") { 25 + Ok(val) => PathBuf::from(val), 26 + Err(_err) => home_dir.join(".config"), 27 + }; 28 + Ok(config_dir.join("tiles")) 20 29 } 21 - } else { 22 - return Err(anyhow::anyhow!(format!( 23 - "Not a valid path {}", 24 - path_buf.to_str().unwrap() 25 - ))); 26 30 } 27 - Ok(format!( 28 - "Memory path set successfully at {}", 29 - path_buf.to_str().unwrap() 30 - )) 31 + 32 + fn get_data_dir(&self) -> Result<PathBuf> { 33 + if cfg!(debug_assertions) { 34 + let base_dir = env::current_dir().context("Failed to fetch CURRENT_DIR")?; 35 + Ok(base_dir.join(".tiles_dev/tiles")) 36 + } else { 37 + let home_dir = env::home_dir().context("Failed to fetch $HOME")?; 38 + let data_dir = match env::var("XDG_DATA_HOME") { 39 + Ok(val) => PathBuf::from(val), 40 + Err(_err) => home_dir.join(".local/share"), 41 + }; 42 + Ok(data_dir.join("tiles")) 43 + } 44 + } 45 + 46 + fn get_lib_dir(&self) -> Result<PathBuf> { 47 + if cfg!(debug_assertions) { 48 + let base_dir = env::current_dir().context("Failed to fetch CURRENT_DIR")?; 49 + Ok(base_dir) 50 + } else { 51 + let home_dir = env::home_dir().context("Failed to fetch $HOME")?; 52 + let data_dir = home_dir.join(".local/lib"); 53 + Ok(data_dir.join("tiles")) 54 + } 55 + } 56 + } 57 + pub fn set_memory_path(path: &str) -> Result<String> { 58 + set_memory_path_with_provider(&DefaultProvider, path) 31 59 } 32 60 33 61 pub fn get_memory_path() -> Result<String> { 34 - let tiles_config_dir = get_config_dir()?; 62 + let tiles_config_dir = DefaultProvider.get_config_dir()?; 35 63 let mut is_memory_path_found: bool = false; 36 64 let mut memory_path: String = String::from(""); 37 65 if tiles_config_dir.is_dir() ··· 49 77 } 50 78 51 79 pub fn get_default_memory_path() -> Result<PathBuf> { 52 - let tiles_data_dir = get_data_dir()?; 80 + let tiles_data_dir = DefaultProvider.get_data_dir()?; 53 81 let memory_path = tiles_data_dir.join("memory"); 54 82 Ok(memory_path) 55 83 } ··· 60 88 Ok(memory_path) 61 89 } 62 90 63 - pub fn get_lib_dir() -> Result<PathBuf> { 64 - if cfg!(debug_assertions) { 65 - let base_dir = env::current_dir().context("Failed to fetch CURRENT_DIR")?; 66 - Ok(base_dir) 67 - } else { 68 - let home_dir = env::home_dir().context("Failed to fetch $HOME")?; 69 - let data_dir = home_dir.join(".local/lib"); 70 - Ok(data_dir.join("tiles")) 71 - } 72 - } 73 - 74 - pub fn get_config_dir() -> Result<PathBuf> { 75 - if cfg!(debug_assertions) { 76 - let base_dir = env::current_dir().context("Failed to fetch CURRENT_DIR")?; 77 - Ok(base_dir.join(".tiles_dev/tiles")) 78 - } else { 79 - let home_dir = env::home_dir().context("Failed to fetch $HOME")?; 80 - let config_dir = match env::var("XDG_CONFIG_HOME") { 81 - Ok(val) => PathBuf::from(val), 82 - Err(_err) => home_dir.join(".config"), 83 - }; 84 - Ok(config_dir.join("tiles")) 85 - } 86 - } 87 - 88 - pub fn get_data_dir() -> Result<PathBuf> { 89 - if cfg!(debug_assertions) { 90 - let base_dir = env::current_dir().context("Failed to fetch CURRENT_DIR")?; 91 - Ok(base_dir.join(".tiles_dev/tiles")) 92 - } else { 93 - let home_dir = env::home_dir().context("Failed to fetch $HOME")?; 94 - let data_dir = match env::var("XDG_DATA_HOME") { 95 - Ok(val) => PathBuf::from(val), 96 - Err(_err) => home_dir.join(".local/share"), 97 - }; 98 - Ok(data_dir.join("tiles")) 99 - } 100 - } 101 - 102 91 pub fn is_memory_model(modelname: &str) -> bool { 103 92 if modelname.contains("mem") { 104 93 return true; 105 94 } 106 95 false 107 96 } 97 + 98 + fn set_memory_path_with_provider<P: ConfigProvider>(provider: &P, path: &str) -> Result<String> { 99 + let path_buf = PathBuf::from_str(path)?; 100 + if path_buf.try_exists()? { 101 + let tiles_config_dir = provider.get_config_dir()?; 102 + let tiles_config_memory_path = tiles_config_dir.join(".memory_path"); 103 + if tiles_config_memory_path.try_exists()? { 104 + fs::write(tiles_config_memory_path, path)?; 105 + } else { 106 + fs::create_dir_all(&tiles_config_dir) 107 + .context("Failed to create tiles config directory")?; 108 + fs::write(tiles_config_memory_path, path) 109 + .context("Failed to write the memory path to .memory_path")?; 110 + } 111 + } else { 112 + return Err(anyhow::anyhow!(format!( 113 + "Not a valid path {}", 114 + path_buf.to_str().unwrap() 115 + ))); 116 + } 117 + Ok(format!( 118 + "Memory path set successfully at {}", 119 + path_buf.to_str().unwrap() 120 + )) 121 + }
+65
tiles/tests/config.rs
··· 1 + use anyhow::Result; 2 + use std::path::PathBuf; 3 + use tiles::utils::config::ConfigProvider; 4 + 5 + #[allow(dead_code)] 6 + struct MockProvider { 7 + base: PathBuf, 8 + } 9 + 10 + impl ConfigProvider for MockProvider { 11 + fn get_config_dir(&self) -> Result<PathBuf> { 12 + Ok(self.base.join("config")) 13 + } 14 + fn get_data_dir(&self) -> Result<PathBuf> { 15 + Ok(self.base.join("data")) 16 + } 17 + fn get_lib_dir(&self) -> Result<PathBuf> { 18 + Ok(self.base.join("lib")) 19 + } 20 + } 21 + 22 + // #[test] 23 + // fn test_get_memory_path() -> Result<()> { 24 + // let _tmp = tempdir()?; 25 + // todo!() 26 + // } 27 + 28 + // #[test] 29 + // fn set_and_get_memory_path_with_mock_provider() -> Result<()> { 30 + // let tmp = tempdir()?; 31 + // let config_dir = tmp.path().join("config"); 32 + // let mem_dir = tmp.path().join("memdir"); 33 + // fs::create_dir_all(&config_dir)?; 34 + // fs::create_dir_all(&mem_dir)?; 35 + 36 + // let provider = MockProvider { 37 + // base: tmp.path().to_path_buf(), 38 + // }; 39 + 40 + // // set 41 + // let path_str = mem_dir.to_str().unwrap(); 42 + // let res = set_memory_path_with_provider(&provider, path_str)?; 43 + // assert!(res.contains("Memory path set successfully")); 44 + 45 + // // verify file content 46 + // let stored = fs::read_to_string(config_dir.join(".memory_path"))?; 47 + // assert_eq!(stored, path_str); 48 + 49 + // // get 50 + // let got = get_memory_path_with_provider(&provider)?; 51 + // assert_eq!(got, path_str); 52 + 53 + // Ok(()) 54 + // } 55 + 56 + // #[test] 57 + // fn default_memory_path_from_provider() -> Result<()> { 58 + // let tmp = tempdir()?; 59 + // let provider = MockProvider { 60 + // base: tmp.path().to_path_buf(), 61 + // }; 62 + // let default = get_default_memory_path_with_provider(&provider)?; 63 + // assert!(default.ends_with("data/memory") || default.ends_with("data\\memory")); 64 + // Ok(()) 65 + // }