A photo manager for VRChat.
0
fork

Configure Feed

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

1#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 3mod frontend_calls; 4mod photosync; 5mod pngmeta; 6mod util; 7mod worldscraper; 8 9use core::time; 10use arboard::Clipboard; 11use frontend_calls::*; 12 13use notify::{ EventKind, RecursiveMode, Watcher }; 14use pngmeta::PNGImage; 15use regex::Regex; 16use util::{ cache::Cache, get_photo_path::get_photo_path }; 17use std::{ env, fs, sync::Mutex, thread }; 18use tauri::{ Emitter, Manager, State, WindowEvent }; 19use tauri_plugin_deep_link::DeepLinkExt; 20 21use crate::frontend_calls::config::get_config_value_string; 22 23// TODO: Linux support 24 25fn main() { 26 #[cfg(target_os = "linux")] 27 std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); // Fix webkitgtk being shit 28 29 let cache = Cache::new(); 30 31 // Double check the app has an install directory 32 let container_folder = dirs::config_dir() 33 .unwrap() 34 .join("PhazeDev/VRChatPhotoManager"); 35 36 match fs::metadata(&container_folder) { 37 Ok(meta) => { 38 if meta.is_file() { 39 panic!("Cannot launch app as the container path is a file not a directory"); 40 } 41 } 42 Err(_) => { 43 let folder = dirs::config_dir().unwrap(); 44 match fs::metadata(&folder) { 45 Ok(meta) => { 46 if meta.is_file() { 47 panic!("Cannot launch app as the container path is a file not a directory"); 48 } 49 } 50 Err(_) => { 51 fs::create_dir(&folder).unwrap(); 52 } 53 } 54 55 let phaz_folder = dirs::config_dir().unwrap().join("PhazeDev"); 56 match fs::metadata(&phaz_folder) { 57 Ok(meta) => { 58 if meta.is_file() { 59 panic!("Cannot launch app as the container path is a file not a directory"); 60 } 61 } 62 Err(_) => { 63 fs::create_dir(&phaz_folder).unwrap(); 64 } 65 } 66 67 fs::create_dir(&container_folder).unwrap(); 68 } 69 } 70 71 let sync_lock_path = dirs::config_dir() 72 .unwrap() 73 .join("PhazeDev/VRChatPhotoManager/.sync_lock"); 74 75 match fs::metadata(&sync_lock_path) { 76 Ok(_) => { 77 fs::remove_file(&sync_lock_path).unwrap(); 78 } 79 Err(_) => {} 80 } 81 82 println!("Loading App..."); 83 let photos_path = util::get_photo_path::get_photo_path(); 84 85 cache.insert("photo-path".into(), photos_path.to_str().unwrap().to_owned()); 86 87 match fs::metadata(&photos_path) { 88 Ok(_) => {} 89 Err(_) => { 90 fs::create_dir(&photos_path).unwrap(); 91 } 92 }; 93 94 // Listen for file updates, store each update in an mpsc channel and send to the frontend 95 let (sender, receiver) = std::sync::mpsc::channel(); 96 let mut watcher = notify::recommended_watcher(move | res: Result<notify::Event, notify::Error> | { 97 match res { 98 Ok(event) => { 99 match event.kind{ 100 EventKind::Remove(_) => { 101 let path = event.paths.first().unwrap(); 102 let name = path.file_name().unwrap().to_str().unwrap().to_owned(); 103 104 let re1 = Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}.png").unwrap(); 105 let re2 = Regex::new(r"(?m)/VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png/gm").unwrap(); 106 107 if 108 re1.is_match(&name) || 109 re2.is_match(&name) 110 { 111 sender.send((2, path.strip_prefix(get_photo_path()).unwrap().to_str().unwrap().to_owned())).unwrap(); 112 } 113 }, 114 EventKind::Create(_) => { 115 let path = event.paths.first().unwrap(); 116 let name = path.file_name().unwrap().to_str().unwrap().to_owned(); 117 118 let re1 = Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}.png").unwrap(); 119 let re2 = Regex::new(r"(?m)/VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png/gm").unwrap(); 120 121 if 122 re1.is_match(&name) || 123 re2.is_match(&name) 124 { 125 thread::sleep(time::Duration::from_millis(1000)); 126 sender.send((1, path.strip_prefix(get_photo_path()).unwrap().to_str().unwrap().to_owned())).unwrap(); 127 } 128 }, 129 _ => {} 130 } 131 }, 132 Err(e) => println!("watch error: {:?}", e), 133 } 134 }).unwrap(); 135 136 println!("Watching dir: {:?}", util::get_photo_path::get_photo_path()); 137 watcher 138 .watch( 139 &util::get_photo_path::get_photo_path(), 140 RecursiveMode::Recursive, 141 ) 142 .unwrap(); 143 144 let clipboard = Clipboard::new().unwrap(); 145 146 tauri::Builder::default() 147 .plugin(tauri_plugin_single_instance::init(| app, _argv, _cwd | { 148 app.get_webview_window("main").unwrap().show().unwrap(); 149 })) 150 .plugin(tauri_plugin_deep_link::init()) 151 .plugin(tauri_plugin_process::init()) 152 .plugin(tauri_plugin_http::init()) 153 .plugin(tauri_plugin_shell::init()) 154 .register_asynchronous_uri_scheme_protocol("photo", |ctx, req, res| { 155 let cache: State<Cache> = ctx.app_handle().state(); 156 util::handle_uri_proto::handle_uri_proto(req, res, cache); 157 }) 158 .on_window_event(|window, event| match event { 159 WindowEvent::CloseRequested { api, .. } => { 160 let val = get_config_value_string("minimise-on-close".into()); 161 if val.is_some() && val.unwrap() == "false"{ return; } 162 163 window.hide().unwrap(); 164 api.prevent_close(); 165 } 166 _ => {} 167 }) 168 .manage(cache) 169 .manage(Mutex::new(clipboard)) 170 .setup(|app| { 171 let handle = app.handle(); 172 173 app.deep_link().register("vrcpm").unwrap(); 174 util::setup_traymenu::setup_traymenu(handle); 175 176 // reads the file update mpsc channel and sends the events to the frontend 177 let window = app.get_webview_window("main").unwrap(); 178 thread::spawn(move || { 179 thread::sleep(time::Duration::from_millis(100)); 180 181 for event in receiver { 182 match event.0 { 183 1 => { 184 window.emit("photo_create", event.1).unwrap(); 185 } 186 2 => { 187 window.emit("photo_remove", event.1).unwrap(); 188 } 189 _ => {} 190 } 191 } 192 }); 193 194 Ok(()) 195 }) 196 .invoke_handler(tauri::generate_handler![ 197 load_photos::load_photos, 198 close_splashscreen::close_splashscreen, 199 load_photo_meta::load_photo_meta, 200 delete_photo::delete_photo, 201 open_url::open_url, 202 open_folder::open_folder, 203 find_world_by_id::find_world_by_id, 204 #[cfg(windows)] 205 start_with_win::start_with_win, 206 get_user_photos_path::get_user_photos_path, 207 change_final_path::change_final_path, 208 sync_photos::sync_photos, 209 util::get_version::get_version, 210 config::set_config_value_string, 211 config::get_config_value_string, 212 config::set_config_value_int, 213 config::get_config_value_int, 214 get_os::get_os, 215 copy_image::copy_image 216 ]) 217 .run(tauri::generate_context!()) 218 .expect("error while running tauri application"); 219}