···1414 - Fixed the control icons do they actually do what they're supposed to do
1515 - Fixed window not focusing when opening from tray
16161717-v0.1.8:
1717+v0.2.0:
1818 - Migrate to tauri v2
1919- - Photo loading is slightly faster1919+2020+ - Photos shouldn't cause the ui to lag while loading
2121+ - Removed the metadata loading screen in favour of loading the metadata just before an image it rendered
2222+ - Added the context menu back to the photo viewer screen
2323+ - Fixed some weird bugs where the world data cache would be ignored
2424+ - Fixed the ui forgetting the user account in some cases where the token stored it still valid
2525+2626+ Dev Stuff:
2727+ - Fixed indentation to be more constistant
···1111tauri-build = { version = "2.0.0-rc", features = [] }
12121313[dependencies]
1414-tauri = { version = "2.0.0-rc", features = ["tray-icon"] }
1414+tauri = { version = "2.0.0-rc", features = ["tray-icon", "image-png"] }
1515serde = { version = "1.0", features = ["derive"] }
1616serde_json = "1.0"
1717open = "5.1.2"
···2424tauri-plugin-shell = "2.0.0-rc.2"
2525tauri-plugin-http = "2.0.0-rc.0"
2626tauri-plugin-process = "2.0.0-rc.0"
2727+image = "0.25.2"
2828+fast_image_resize = { version = "4.2.1", features = [ "image" ] }
27292830[features]
2931# this feature is used for production builds or when `devPath` points to the filesystem
···11-{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-start-dragging","shell:allow-open",{"identifier":"http:default","allow":[{"url":"https://photos.phazed.xyz/*"},{"url":"https://photos-cdn.phazed.xyz/*"}]},"process:allow-restart","shell:default","http:default","process:default"]}}11+{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-toggle-maximize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-start-dragging","shell:allow-open",{"identifier":"http:default","allow":[{"url":"https://photos.phazed.xyz/*"},{"url":"https://photos-cdn.phazed.xyz/*"}]},"process:allow-restart","shell:default","http:default","process:default"]}}
+419-360
src-tauri/src/main.rs
···55mod worldscraper;
6677use core::time;
88+use image::{ codecs::png::{ PngDecoder, PngEncoder }, DynamicImage, ImageEncoder };
99+use fast_image_resize::{ images::Image, IntoImageView, ResizeOptions, Resizer };
810use mslnk::ShellLink;
911use notify::{EventKind, RecursiveMode, Watcher};
1012use pngmeta::PNGImage;
1113use regex::Regex;
1214use std::{
1313- env, fs,
1414- io::Read,
1515- path,
1616- process::{self, Command},
1717- thread,
1818- time::Duration,
1515+ env, fs,
1616+ io::{ BufReader, Read },
1717+ path,
1818+ process::{self, Command},
1919+ thread,
2020+ time::Duration,
1921};
2022use tauri::{
2121- http::Response, menu::{ MenuBuilder, MenuItemBuilder }, tray::{ MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent }, AppHandle, Emitter, Manager, WindowEvent
2323+ http::Response, menu::{MenuBuilder, MenuItemBuilder}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, AppHandle, Emitter, Manager, WindowEvent
2224};
2325use worldscraper::World;
2626+2727+// TODO: for the love of fuck please seperate this file out into multiple files at some point
2828+// TODO: Linux support
24292530// Scans all files under the "Pictures/VRChat" path
2631// then sends the list of photos to the frontend
2732#[derive(Clone, serde::Serialize)]
2833struct PhotosLoadedResponse {
2929- photos: Vec<path::PathBuf>,
3030- size: usize,
3434+ photos: Vec<path::PathBuf>,
3535+ size: usize,
3136}
32373338const VERSION: &str = env!("CARGO_PKG_VERSION");
34393540pub fn get_photo_path() -> path::PathBuf {
3636- let config_path = dirs::home_dir()
3737- .unwrap()
3838- .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.photos_path");
4141+ let config_path = dirs::home_dir()
4242+ .unwrap()
4343+ .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.photos_path");
39444040- match fs::read_to_string(config_path) {
4141- Ok(path) => {
4242- if path
4343- != dirs::picture_dir()
4444- .unwrap()
4545- .join("VRChat")
4646- .to_str()
4747- .unwrap()
4848- .to_owned()
4949- {
5050- path::PathBuf::from(path)
5151- } else {
5252- dirs::picture_dir().unwrap().join("VRChat")
5353- }
5454- }
5555- Err(_) => dirs::picture_dir().unwrap().join("VRChat"),
4545+ match fs::read_to_string(config_path) {
4646+ Ok(path) => {
4747+ if path
4848+ != dirs::picture_dir()
4949+ .unwrap()
5050+ .join("VRChat")
5151+ .to_str()
5252+ .unwrap()
5353+ .to_owned()
5454+ {
5555+ path::PathBuf::from(path)
5656+ } else {
5757+ dirs::picture_dir().unwrap().join("VRChat")
5858+ }
5659 }
6060+ Err(_) => dirs::picture_dir().unwrap().join("VRChat"),
6161+ }
5762}
58635964#[tauri::command]
6065fn close_splashscreen(window: tauri::Window) {
6161- window.get_webview_window("main").unwrap().show().unwrap();
6666+ window.get_webview_window("main").unwrap().show().unwrap();
6267}
63686469#[tauri::command]
6570fn start_user_auth() {
6666- open::that("https://photos.phazed.xyz/api/v1/auth").unwrap();
7171+ open::that("https://photos.phazed.xyz/api/v1/auth").unwrap();
6772}
68736974#[tauri::command]
7075fn open_url(url: &str) {
7171- open::that(url).unwrap();
7676+ open::that(url).unwrap();
7777+}
7878+7979+#[tauri::command]
8080+fn open_folder(url: &str) {
8181+ Command::new("explorer.exe")
8282+ .arg(format!("/select,{}", url))
8383+ .spawn()
8484+ .unwrap();
7285}
73867487// Check if the photo config file exists
7588// if not just return the default vrchat path
7689#[tauri::command]
7790fn get_user_photos_path() -> path::PathBuf {
7878- get_photo_path()
9191+ get_photo_path()
7992}
80938194// When the user changes the start with windows toggle
8295// create and delete the shortcut from the startup folder
8396#[tauri::command]
8497fn start_with_win(start: bool) {
8585- thread::spawn(move || {
8686- if start {
8787- let target = dirs::home_dir()
8888- .unwrap()
8989- .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\vrchat-photo-manager.exe");
9090- match fs::metadata(&target) {
9191- Ok(_) => {
9292- let lnk = dirs::home_dir().unwrap().join("AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\VRChat Photo Manager.lnk");
9898+ thread::spawn(move || {
9999+ if start {
100100+ let target = dirs::home_dir()
101101+ .unwrap()
102102+ .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\vrchat-photo-manager.exe");
931039494- let sl = ShellLink::new(target).unwrap();
9595- sl.create_lnk(lnk).unwrap();
9696- }
9797- Err(_) => {}
9898- }
9999- } else {
100100- let lnk = dirs::home_dir().unwrap().join("AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\VRChat Photo Manager.lnk");
101101- fs::remove_file(lnk).unwrap();
104104+ match fs::metadata(&target) {
105105+ Ok(_) => {
106106+ let lnk = dirs::home_dir().unwrap().join("AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\VRChat Photo Manager.lnk");
107107+108108+ let sl = ShellLink::new(target).unwrap();
109109+ sl.create_lnk(lnk).unwrap();
102110 }
103103- });
111111+ Err(_) => {}
112112+ }
113113+ } else {
114114+ let lnk = dirs::home_dir().unwrap().join("AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\VRChat Photo Manager.lnk");
115115+ fs::remove_file(lnk).unwrap();
116116+ }
117117+ });
104118}
105119106120// Load vrchat world data
107121#[tauri::command]
108122fn find_world_by_id(world_id: String, window: tauri::Window) {
109109- thread::spawn(move || {
110110- let world = World::new(world_id);
111111- window.emit("world_data", world).unwrap();
112112- });
123123+ thread::spawn(move || {
124124+ let world = World::new(world_id);
125125+ window.emit("world_data", world).unwrap();
126126+ });
113127}
114128115129// On requested sync the photos to the cloud
116130#[tauri::command]
117131fn sync_photos(token: String, window: tauri::Window) {
118118- thread::spawn(move || {
119119- photosync::sync_photos(token, get_photo_path(), window);
120120- });
132132+ thread::spawn(move || {
133133+ photosync::sync_photos(token, get_photo_path(), window);
134134+ });
121135}
122136123137#[tauri::command]
124138fn load_photos(window: tauri::Window) {
125125- thread::spawn(move || {
126126- let base_dir = get_photo_path();
139139+ thread::spawn(move || {
140140+ let base_dir = get_photo_path();
127141128128- let mut photos: Vec<path::PathBuf> = Vec::new();
129129- let mut size: usize = 0;
142142+ let mut photos: Vec<path::PathBuf> = Vec::new();
143143+ let mut size: usize = 0;
130144131131- for folder in fs::read_dir(&base_dir).unwrap() {
132132- let f = folder.unwrap();
145145+ for folder in fs::read_dir(&base_dir).unwrap() {
146146+ let f = folder.unwrap();
133147134134- if f.metadata().unwrap().is_dir() {
135135- for photo in fs::read_dir(f.path()).unwrap() {
136136- let p = photo.unwrap();
148148+ if f.metadata().unwrap().is_dir() {
149149+ for photo in fs::read_dir(f.path()).unwrap() {
150150+ let p = photo.unwrap();
137151138138- if p.metadata().unwrap().is_file() {
139139- let fname = p.path();
152152+ if p.metadata().unwrap().is_file() {
153153+ let fname = p.path();
140154141141- 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();
142142- let re2 = Regex::new(
143143- 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").unwrap();
155155+ 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();
156156+ let re2 = Regex::new(
157157+ 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").unwrap();
144158145145- if re1.is_match(p.file_name().to_str().unwrap())
146146- || re2.is_match(p.file_name().to_str().unwrap())
147147- {
148148- let path = fname.to_path_buf().clone();
149149- let metadata = fs::metadata(&path).unwrap();
159159+ if re1.is_match(p.file_name().to_str().unwrap())
160160+ || re2.is_match(p.file_name().to_str().unwrap())
161161+ {
162162+ let path = fname.to_path_buf().clone();
163163+ let metadata = fs::metadata(&path).unwrap();
150164151151- if metadata.is_file() {
152152- size += metadata.len() as usize;
165165+ if metadata.is_file() {
166166+ size += metadata.len() as usize;
153167154154- let path = path.strip_prefix(&base_dir).unwrap().to_path_buf();
155155- photos.push(path);
156156- }
157157- } else {
158158- println!("Ignoring {:#?} as it doesn't match regex", p.file_name());
159159- }
160160- } else {
161161- println!("Ignoring {:#?} as it is a directory", p.file_name());
162162- }
163163- }
168168+ let path = path.strip_prefix(&base_dir).unwrap().to_path_buf();
169169+ photos.push(path);
170170+ }
164171 } else {
165165- println!("Ignoring {:#?} as it isn't a directory", f.file_name());
172172+ println!("Ignoring {:#?} as it doesn't match regex", p.file_name());
166173 }
174174+ } else {
175175+ println!("Ignoring {:#?} as it is a directory", p.file_name());
176176+ }
167177 }
178178+ } else {
179179+ println!("Ignoring {:#?} as it isn't a directory", f.file_name());
180180+ }
181181+ }
168182169169- println!("Found {} photos", photos.len());
170170- window
171171- .emit("photos_loaded", PhotosLoadedResponse { photos, size })
172172- .unwrap();
173173- });
183183+ println!("Found {} photos", photos.len());
184184+ window
185185+ .emit("photos_loaded", PhotosLoadedResponse { photos, size })
186186+ .unwrap();
187187+ });
174188}
175189176190// Reads the PNG file and loads the image metadata from it
177191// then sends the metadata to the frontend, returns width, height, colour depth and so on... more info "pngmeta.rs"
178192#[tauri::command]
179193fn load_photo_meta(photo: &str, window: tauri::Window) {
180180- let photo = photo.to_string();
194194+ let photo = photo.to_string();
181195182182- thread::spawn(move || {
183183- let base_dir = get_photo_path().join(&photo);
196196+ thread::spawn(move || {
197197+ let base_dir = get_photo_path().join(&photo);
184198185185- let file = fs::File::open(base_dir.clone());
199199+ let file = fs::File::open(base_dir.clone());
186200187187- match file {
188188- Ok(mut file) => {
189189- let mut buffer = Vec::new();
201201+ match file {
202202+ Ok(mut file) => {
203203+ let mut buffer = Vec::new();
190204191191- let _out = file.read_to_end(&mut buffer);
192192- window
193193- .emit("photo_meta_loaded", PNGImage::new(buffer, photo))
194194- .unwrap();
195195- }
196196- Err(_) => {
197197- println!("Cannot read image file");
198198- }
199199- }
200200- });
205205+ let _out = file.read_to_end(&mut buffer);
206206+ window
207207+ .emit("photo_meta_loaded", PNGImage::new(buffer, photo))
208208+ .unwrap();
209209+ }
210210+ Err(_) => {
211211+ println!("Cannot read image file");
212212+ }
213213+ }
214214+ });
201215}
202216203217// Delete a photo when the users confirms the prompt in the ui
204218#[tauri::command]
205219fn delete_photo(path: String, token: String, is_syncing: bool) {
206206- thread::spawn(move || {
207207- let p = get_photo_path().join(&path);
208208- fs::remove_file(p).unwrap();
220220+ thread::spawn(move || {
221221+ let p = get_photo_path().join(&path);
222222+ fs::remove_file(p).unwrap();
209223210210- let photo = path.split("\\").last().unwrap();
224224+ let photo = path.split("\\").last().unwrap();
211225212212- if is_syncing {
213213- let client = reqwest::blocking::Client::new();
214214- client
215215- .delete(format!(
216216- "https://photos-cdn.phazed.xyz/api/v1/photos?token={}&photo={}",
217217- token, photo
218218- ))
219219- .timeout(Duration::from_secs(120))
220220- .send()
221221- .unwrap();
222222- }
223223- });
226226+ if is_syncing {
227227+ let client = reqwest::blocking::Client::new();
228228+ client
229229+ .delete(format!(
230230+ "https://photos-cdn.phazed.xyz/api/v1/photos?token={}&photo={}",
231231+ token, photo
232232+ ))
233233+ .timeout(Duration::from_secs(120))
234234+ .send()
235235+ .unwrap();
236236+ }
237237+ });
224238}
225239226240#[tauri::command]
227241fn change_final_path(new_path: &str) {
228228- let config_path = dirs::home_dir()
229229- .unwrap()
230230- .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.photos_path");
231231- fs::write(&config_path, new_path.as_bytes()).unwrap();
242242+ let config_path = dirs::home_dir()
243243+ .unwrap()
244244+ .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.photos_path");
232245233233- match fs::metadata(&new_path) {
234234- Ok(_) => {}
235235- Err(_) => {
236236- fs::create_dir(&new_path).unwrap();
237237- }
238238- };
246246+ fs::write(&config_path, new_path.as_bytes()).unwrap();
247247+248248+ match fs::metadata(&new_path) {
249249+ Ok(_) => {}
250250+ Err(_) => {
251251+ fs::create_dir(&new_path).unwrap();
252252+ }
253253+ };
239254}
240255241256#[tauri::command]
242257fn get_version() -> String {
243243- String::from(VERSION)
258258+ String::from(VERSION)
244259}
245260246261#[tauri::command]
247262fn relaunch() {
248248- let container_folder = dirs::home_dir()
249249- .unwrap()
250250- .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
263263+ let container_folder = dirs::home_dir()
264264+ .unwrap()
265265+ .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
251266252252- let mut cmd = Command::new(&container_folder.join("./vrchat-photo-manager.exe"));
253253- cmd.current_dir(container_folder);
254254- cmd.spawn().expect("Cannot run updater");
267267+ let mut cmd = Command::new(&container_folder.join("./vrchat-photo-manager.exe"));
268268+ cmd.current_dir(container_folder);
269269+ cmd.spawn().expect("Cannot run updater");
255270256256- process::exit(0);
271271+ process::exit(0);
257272}
258273259274fn main() {
260260- std::env::set_var(
261261- "WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS",
262262- "--ignore-gpu-blacklist",
263263- );
264264- tauri_plugin_deep_link::prepare("uk.phaz.vrcpm");
275275+ tauri_plugin_deep_link::prepare("uk.phaz.vrcpm");
276276+277277+ // Double check the app has an install directory
278278+ let container_folder = dirs::home_dir()
279279+ .unwrap()
280280+ .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
265281266266- // Double check the app has an install directory
267267- let container_folder = dirs::home_dir()
268268- .unwrap()
269269- .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
270270- match fs::metadata(&container_folder) {
271271- Ok(meta) => {
272272- if meta.is_file() {
273273- panic!("Cannot launch app as the container path is a file not a directory");
274274- }
275275- }
276276- Err(_) => {
277277- fs::create_dir(&container_folder).unwrap();
278278- }
282282+ match fs::metadata(&container_folder) {
283283+ Ok(meta) => {
284284+ if meta.is_file() {
285285+ panic!("Cannot launch app as the container path is a file not a directory");
286286+ }
279287 }
288288+ Err(_) => {
289289+ fs::create_dir(&container_folder).unwrap();
290290+ }
291291+ }
280292281281- let sync_lock_path = dirs::home_dir()
282282- .unwrap()
283283- .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.sync_lock");
284284- match fs::metadata(&sync_lock_path) {
285285- Ok(_) => {
286286- fs::remove_file(&sync_lock_path).unwrap();
287287- }
288288- Err(_) => {}
293293+ let sync_lock_path = dirs::home_dir()
294294+ .unwrap()
295295+ .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.sync_lock");
296296+297297+ match fs::metadata(&sync_lock_path) {
298298+ Ok(_) => {
299299+ fs::remove_file(&sync_lock_path).unwrap();
289300 }
301301+ Err(_) => {}
302302+ }
290303291291- println!("Loading App...");
292292- let photos_path = get_photo_path();
304304+ println!("Loading App...");
305305+ let photos_path = get_photo_path();
293306294294- match fs::metadata(&photos_path) {
295295- Ok(_) => {}
296296- Err(_) => {
297297- fs::create_dir(&photos_path).unwrap();
298298- }
299299- };
307307+ match fs::metadata(&photos_path) {
308308+ Ok(_) => {}
309309+ Err(_) => {
310310+ fs::create_dir(&photos_path).unwrap();
311311+ }
312312+ };
300313301301- let args: Vec<String> = env::args().collect();
314314+ let args: Vec<String> = env::args().collect();
302315303303- let mut update = true;
304304- for arg in args {
305305- if arg == "--no-update" {
306306- update = false;
307307- }
316316+ let mut update = true;
317317+ for arg in args {
318318+ if arg == "--no-update" {
319319+ update = false;
308320 }
321321+ }
309322310310- if update {
311311- // Auto update
312312- thread::spawn(move || {
313313- let client = reqwest::blocking::Client::new();
323323+ if update {
324324+ // Auto update
325325+ thread::spawn(move || {
326326+ let client = reqwest::blocking::Client::new();
314327315315- let latest_version = client
316316- .get("https://cdn.phaz.uk/vrcpm/latest")
317317- .send()
318318- .unwrap()
319319- .text()
320320- .unwrap();
328328+ let latest_version = client
329329+ .get("https://cdn.phaz.uk/vrcpm/latest")
330330+ .send()
331331+ .unwrap()
332332+ .text()
333333+ .unwrap();
321334322322- if latest_version != VERSION {
323323- match fs::metadata(&container_folder.join("./updater.exe")) {
324324- Ok(_) => {}
325325- Err(_) => {
326326- let latest_installer = client
327327- .get("https://cdn.phaz.uk/vrcpm/vrcpm-installer.exe")
328328- .timeout(Duration::from_secs(120))
329329- .send()
330330- .unwrap()
331331- .bytes()
332332- .unwrap();
335335+ if latest_version != VERSION {
336336+ match fs::metadata(&container_folder.join("./updater.exe")) {
337337+ Ok(_) => {}
338338+ Err(_) => {
339339+ let latest_installer = client
340340+ .get("https://cdn.phaz.uk/vrcpm/vrcpm-installer.exe")
341341+ .timeout(Duration::from_secs(120))
342342+ .send()
343343+ .unwrap()
344344+ .bytes()
345345+ .unwrap();
333346334334- fs::write(&container_folder.join("./updater.exe"), latest_installer)
335335- .unwrap();
336336- }
337337- }
347347+ fs::write(&container_folder.join("./updater.exe"), latest_installer)
348348+ .unwrap();
349349+ }
350350+ }
338351339339- let mut cmd = Command::new(&container_folder.join("./updater.exe"));
340340- cmd.current_dir(container_folder);
341341- cmd.spawn().expect("Cannot run updater");
352352+ let mut cmd = Command::new(&container_folder.join("./updater.exe"));
353353+ cmd.current_dir(container_folder);
354354+ cmd.spawn().expect("Cannot run updater");
342355343343- process::exit(0);
344344- }
345345- });
346346- }
356356+ process::exit(0);
357357+ }
358358+ });
359359+ }
347360348348- // Listen for file updates, store each update in an mpsc channel and send to the frontend
349349- let (sender, receiver) = std::sync::mpsc::channel();
350350- let mut watcher = notify::recommended_watcher(move | res: Result<notify::Event, notify::Error> | {
361361+ // Listen for file updates, store each update in an mpsc channel and send to the frontend
362362+ let (sender, receiver) = std::sync::mpsc::channel();
363363+ let mut watcher = notify::recommended_watcher(move | res: Result<notify::Event, notify::Error> | {
351364 match res {
352352- Ok(event) => {
365365+ Ok(event) => {
353366 match event.kind{
354367 EventKind::Remove(_) => {
355368 let path = event.paths.first().unwrap();
···385398 }
386399 }).unwrap();
387400388388- watcher
389389- .watch(&get_photo_path(), RecursiveMode::Recursive)
390390- .unwrap();
401401+ watcher
402402+ .watch(&get_photo_path(), RecursiveMode::Recursive)
403403+ .unwrap();
404404+405405+ tauri::Builder::default()
406406+ .plugin(tauri_plugin_process::init())
407407+ .plugin(tauri_plugin_http::init())
408408+ .plugin(tauri_plugin_shell::init())
409409+ .register_asynchronous_uri_scheme_protocol("photo", move |_app, request, responder| {
410410+ thread::spawn(move || {
411411+ // Loads the requested image file, sends data back to the user
412412+ let uri = request.uri();
413413+414414+ if request.method() != "GET" {
415415+ responder.respond(
416416+ Response::builder()
417417+ .status(404)
418418+ .header("Access-Control-Allow-Origin", "*")
419419+ .body(Vec::new())
420420+ .unwrap(),
421421+ );
422422+423423+ return;
424424+ }
425425+426426+ let path = uri.path().split_at(1).1;
427427+ let file = fs::File::open(path);
428428+429429+ match file {
430430+ Ok(mut file) => {
431431+ match uri.query().unwrap(){
432432+ "downscale" => {
433433+ let decoder = PngDecoder::new(BufReader::new(&file)).unwrap();
434434+ let src_image = DynamicImage::from_decoder(decoder).unwrap();
435435+436436+ let size_multiplier: f32 = 200.0 / src_image.height() as f32;
437437+438438+ let dst_width = (src_image.width() as f32 * size_multiplier).floor() as u32;
439439+ let dst_height: u32 = 200;
440440+441441+ let mut dst_image = Image::new(dst_width, dst_height, src_image.pixel_type().unwrap());
442442+ let mut resizer = Resizer::new();
391443392392- tauri::Builder::default()
393393- .plugin(tauri_plugin_process::init())
394394- .plugin(tauri_plugin_http::init())
395395- .plugin(tauri_plugin_shell::init())
396396- .register_asynchronous_uri_scheme_protocol("photo", move |_app, request, responder| {
397397- thread::spawn(move || {
398398- // Loads the requested image file, sends data back to the user
399399- let uri = request.uri();
444444+ let opts = ResizeOptions::new()
445445+ .resize_alg(fast_image_resize::ResizeAlg::Nearest);
400446401401- if request.method() != "GET" {
402402- responder.respond(Response::builder()
403403- .status(404)
404404- .header("Access-Control-Allow-Origin", "*")
405405- .body(Vec::new())
406406- .unwrap());
447447+ resizer.resize(&src_image, &mut dst_image, Some(&opts)).unwrap();
407448408408- return;
409409- }
449449+ let mut buf = Vec::new();
450450+ let encoder = PngEncoder::new(&mut buf);
410451411411- let path = uri.path().split_at(1).1;
412412- let file = fs::File::open(path);
452452+ encoder.write_image(dst_image.buffer(), dst_width, dst_height, src_image.color().into()).unwrap();
413453414414- match file {
415415- Ok(mut file) => {
416416- let mut buffer = Vec::new();
454454+ let res = Response::builder()
455455+ .status(200)
456456+ .header("Access-Control-Allow-Origin", "*")
457457+ .body(buf)
458458+ .unwrap();
417459418418- let _out = file.read_to_end(&mut buffer);
460460+ responder.respond(res);
461461+ },
462462+ _ => {
463463+ let mut buf = Vec::new();
464464+ file.read_to_end(&mut buf).unwrap();
419465420420- let res = Response::builder()
421421- .status(200)
422422- .header("Access-Control-Allow-Origin", "*")
423423- .body(buffer)
424424- .unwrap();
466466+ let res = Response::builder()
467467+ .status(200)
468468+ .header("Access-Control-Allow-Origin", "*")
469469+ .body(buf)
470470+ .unwrap();
425471426426- responder.respond(res);
427427- }
428428- Err(_) => {
429429- responder.respond(Response::builder()
430430- .status(404)
431431- .header("Access-Control-Allow-Origin", "*")
432432- .body(b"File Not Found")
433433- .unwrap());
434434- }
472472+ responder.respond(res);
435473 }
436436- });
437437- })
438438- .on_window_event(| window, event | match event {
439439- WindowEvent::CloseRequested { api, .. } => {
440440- window.hide().unwrap();
441441- api.prevent_close();
474474+ }
442475 }
443443- _ => {}
444444- })
445445- .setup(|app| {
446446- let handle = app.handle().clone();
476476+ Err(_) => {
477477+ responder.respond(
478478+ Response::builder()
479479+ .status(404)
480480+ .header("Access-Control-Allow-Origin", "*")
481481+ .body(b"File Not Found")
482482+ .unwrap(),
483483+ );
484484+ }
485485+ }
486486+ });
487487+ })
488488+ .on_window_event(|window, event| match event {
489489+ WindowEvent::CloseRequested { api, .. } => {
490490+ window.hide().unwrap();
491491+ api.prevent_close();
492492+ }
493493+ _ => {}
494494+ })
495495+ .setup(|app| {
496496+ let handle = app.handle().clone();
447497448448- // Setup the tray icon and menu buttons
449449- let quit = MenuItemBuilder::new("Quit").id("quit").build(&handle).unwrap();
450450- let hide = MenuItemBuilder::new("Hide / Show").id("hide").build(&handle).unwrap();
498498+ // Setup the tray icon and menu buttons
499499+ let quit = MenuItemBuilder::new("Quit")
500500+ .id("quit")
501501+ .build(&handle)
502502+ .unwrap();
451503452452- let tray_menu = MenuBuilder::new(&handle)
453453- .items(&[ &quit, &hide ])
454454- .build().unwrap();
504504+ let hide = MenuItemBuilder::new("Hide / Show")
505505+ .id("hide")
506506+ .build(&handle)
507507+ .unwrap();
455508456456- TrayIconBuilder::with_id("vrcpm-tray")
457457- .menu(&tray_menu)
458458- .on_menu_event(move | app: &AppHandle, event |{
459459- match event.id().as_ref() {
460460- "quit" => {
461461- std::process::exit(0);
462462- }
463463- "hide" => {
464464- let window = app.get_webview_window("main").unwrap();
509509+ let tray_menu = MenuBuilder::new(&handle)
510510+ .items(&[&quit, &hide])
511511+ .build()
512512+ .unwrap();
465513466466- if window.is_visible().unwrap() {
467467- window.hide().unwrap();
468468- } else {
469469- window.show().unwrap();
470470- window.set_focus().unwrap();
471471- }
472472- }
473473- _ => {}
474474- }
475475- })
476476- .on_tray_icon_event(| tray, event |{
477477- if let TrayIconEvent::Click {
478478- button: MouseButton::Left,
479479- button_state: MouseButtonState::Up,
480480- ..
481481- } = event{
482482- let window = tray.app_handle().get_webview_window("main").unwrap();
514514+ TrayIconBuilder::with_id("main")
515515+ .icon(tauri::image::Image::from_bytes(include_bytes!("../icons/32x32.png")).unwrap())
516516+ .menu(&tray_menu)
517517+ .on_menu_event(move |app: &AppHandle, event| match event.id().as_ref() {
518518+ "quit" => {
519519+ std::process::exit(0);
520520+ }
521521+ "hide" => {
522522+ let window = app.get_webview_window("main").unwrap();
483523484484- window.show().unwrap();
485485- window.set_focus().unwrap();
486486- }
487487- })
488488- .build(&handle).unwrap();
524524+ if window.is_visible().unwrap() {
525525+ window.hide().unwrap();
526526+ } else {
527527+ window.show().unwrap();
528528+ window.set_focus().unwrap();
529529+ }
530530+ }
531531+ _ => {}
532532+ })
533533+ .on_tray_icon_event(|tray, event| {
534534+ if let TrayIconEvent::Click {
535535+ button: MouseButton::Left,
536536+ button_state: MouseButtonState::Up,
537537+ ..
538538+ } = event
539539+ {
540540+ let window = tray.app_handle().get_webview_window("main").unwrap();
489541490490- // Register "deep link" for authentication via vrcpm://
491491- tauri_plugin_deep_link::register("vrcpm", move |request| {
492492- let mut command: u8 = 0;
493493- let mut index: u8 = 0;
542542+ window.show().unwrap();
543543+ window.set_focus().unwrap();
544544+ }
545545+ })
546546+ .build(&handle)
547547+ .unwrap();
548548+ // Register "deep link" for authentication via vrcpm://
549549+ tauri_plugin_deep_link::register("vrcpm", move |request| {
550550+ let mut command: u8 = 0;
551551+ let mut index: u8 = 0;
494552495495- for part in request.split('/').into_iter() {
496496- index += 1;
553553+ for part in request.split('/').into_iter() {
554554+ index += 1;
497555498498- if index == 3 && part == "auth-callback" {
499499- command = 1;
500500- }
556556+ if index == 3 && part == "auth-callback" {
557557+ command = 1;
558558+ }
501559502502- if index == 3 && part == "auth-denied" {
503503- handle.emit("auth-denied", "null").unwrap();
504504- }
560560+ if index == 3 && part == "auth-denied" {
561561+ handle.emit("auth-denied", "null").unwrap();
562562+ }
505563506506- if index == 4 && command == 1 {
507507- handle.emit("auth-callback", part).unwrap();
508508- }
509509- }
510510- })
511511- .unwrap();
564564+ if index == 4 && command == 1 {
565565+ handle.emit("auth-callback", part).unwrap();
566566+ }
567567+ }
568568+ })
569569+ .unwrap();
512570513513- // I hate this approach but i have no clue how else to do this...
514514- // reads the mpsc channel and sends the events to the frontend
515515- let window = app.get_webview_window("main").unwrap();
516516- thread::spawn(move || {
517517- thread::sleep(time::Duration::from_millis(100));
571571+ // I hate this approach but i have no clue how else to do this...
572572+ // reads the mpsc channel and sends the events to the frontend
573573+ let window = app.get_webview_window("main").unwrap();
574574+ thread::spawn(move || {
575575+ thread::sleep(time::Duration::from_millis(100));
518576519519- for event in receiver {
520520- match event.0 {
521521- 1 => {
522522- window.emit("photo_create", event.1).unwrap();
523523- }
524524- 2 => {
525525- window.emit("photo_remove", event.1).unwrap();
526526- }
527527- _ => {}
528528- }
529529- }
530530- });
577577+ for event in receiver {
578578+ match event.0 {
579579+ 1 => {
580580+ window.emit("photo_create", event.1).unwrap();
581581+ }
582582+ 2 => {
583583+ window.emit("photo_remove", event.1).unwrap();
584584+ }
585585+ _ => {}
586586+ }
587587+ }
588588+ });
531589532532- Ok(())
533533- })
534534- .invoke_handler(tauri::generate_handler![
535535- start_user_auth,
536536- load_photos,
537537- close_splashscreen,
538538- load_photo_meta,
539539- delete_photo,
540540- open_url,
541541- find_world_by_id,
542542- start_with_win,
543543- get_user_photos_path,
544544- change_final_path,
545545- sync_photos,
546546- get_version,
547547- relaunch
548548- ])
549549- .run(tauri::generate_context!())
550550- .expect("error while running tauri application");
590590+ Ok(())
591591+ })
592592+ .invoke_handler(tauri::generate_handler![
593593+ start_user_auth,
594594+ load_photos,
595595+ close_splashscreen,
596596+ load_photo_meta,
597597+ delete_photo,
598598+ open_url,
599599+ open_folder,
600600+ find_world_by_id,
601601+ start_with_win,
602602+ get_user_photos_path,
603603+ change_final_path,
604604+ sync_photos,
605605+ get_version,
606606+ relaunch
607607+ ])
608608+ .run(tauri::generate_context!())
609609+ .expect("error while running tauri application");
551610}
+204-200
src-tauri/src/photosync.rs
···7788#[derive(Clone, Serialize)]
99struct PhotoUploadMeta {
1010- photos_uploading: usize,
1111- photos_total: usize,
1010+ photos_uploading: usize,
1111+ photos_total: usize,
1212}
13131414pub fn sync_photos(token: String, path: path::PathBuf, window: tauri::Window) {
1515- let sync_lock_path = dirs::home_dir()
1616- .unwrap()
1717- .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.sync_lock");
1818- match fs::metadata(&sync_lock_path) {
1919- Ok(_) => {
2020- return;
2121- }
2222- Err(_) => {}
1515+ let sync_lock_path = dirs::home_dir()
1616+ .unwrap()
1717+ .join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.sync_lock");
1818+1919+ match fs::metadata(&sync_lock_path) {
2020+ Ok(_) => {
2121+ return;
2322 }
2323+ Err(_) => {}
2424+ }
24252525- fs::write(&sync_lock_path, "Currently Syncing").unwrap();
2626+ fs::write(&sync_lock_path, "Currently Syncing").unwrap();
26272727- match fs::metadata(&path) {
2828- Ok(_) => {}
2929- Err(_) => {
3030- fs::create_dir(&path).unwrap();
3131- }
3232- };
2828+ match fs::metadata(&path) {
2929+ Ok(_) => {}
3030+ Err(_) => {
3131+ fs::create_dir(&path).unwrap();
3232+ }
3333+ };
33343434- let mut photos: Vec<String> = Vec::new();
3535+ let mut photos: Vec<String> = Vec::new();
35363636- for folder in fs::read_dir(&path).unwrap() {
3737- let f = folder.unwrap();
3737+ for folder in fs::read_dir(&path).unwrap() {
3838+ let f = folder.unwrap();
38393939- if f.metadata().unwrap().is_dir() {
4040- match fs::read_dir(f.path()) {
4141- Ok(dir) => {
4242- for photo in dir {
4343- let p = photo.unwrap();
4040+ if f.metadata().unwrap().is_dir() {
4141+ match fs::read_dir(f.path()) {
4242+ Ok(dir) => {
4343+ for photo in dir {
4444+ let p = photo.unwrap();
44454545- 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();
4646- let re2 = Regex::new(
4747- 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").unwrap();
4646+ 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();
4747+ let re2 = Regex::new(
4848+ 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").unwrap();
48494949- if re1.is_match(p.file_name().to_str().unwrap())
5050- || re2.is_match(p.file_name().to_str().unwrap())
5151- {
5252- photos.push(p.file_name().into_string().unwrap());
5353- }
5454- }
5555- }
5656- Err(_) => {}
5050+ if re1.is_match(p.file_name().to_str().unwrap())
5151+ || re2.is_match(p.file_name().to_str().unwrap())
5252+ {
5353+ photos.push(p.file_name().into_string().unwrap());
5754 }
5555+ }
5856 }
5757+ Err(_) => {}
5858+ }
5959 }
6060+ }
60616161- let body = reqwest::blocking::get(format!(
6262- "https://photos-cdn.phazed.xyz/api/v1/photos/exists?token={}",
6363- &token
6464- ))
6565- .unwrap()
6666- .text()
6767- .unwrap();
6262+ let body = reqwest::blocking::get(format!(
6363+ "https://photos-cdn.phazed.xyz/api/v1/photos/exists?token={}",
6464+ &token
6565+ ))
6666+ .unwrap()
6767+ .text()
6868+ .unwrap();
68696969- let body: Value = serde_json::from_str(&body).unwrap();
7070+ let body: Value = serde_json::from_str(&body).unwrap();
70717171- let mut photos_to_upload: Vec<String> = Vec::new();
7272- let uploaded_photos = body["files"].as_array().unwrap();
7272+ let mut photos_to_upload: Vec<String> = Vec::new();
7373+ let uploaded_photos = body["files"].as_array().unwrap();
73747474- let photos_len = photos.len();
7575+ let photos_len = photos.len();
75767676- for photo in &photos {
7777- let mut found_photo = false;
7777+ for photo in &photos {
7878+ let mut found_photo = false;
78797979- for uploaded_photo in uploaded_photos {
8080- if photo == uploaded_photo.as_str().unwrap() {
8181- found_photo = true;
8282- break;
8383- }
8484- }
8080+ for uploaded_photo in uploaded_photos {
8181+ if photo == uploaded_photo.as_str().unwrap() {
8282+ found_photo = true;
8383+ break;
8484+ }
8585+ }
85868686- if !found_photo {
8787- photos_to_upload.push(photo.clone());
8888- }
8787+ if !found_photo {
8888+ photos_to_upload.push(photo.clone());
8989 }
9090+ }
90919191- window
9292- .emit(
9393- "photos-upload-meta",
9494- PhotoUploadMeta {
9595- photos_uploading: photos_to_upload.len(),
9696- photos_total: photos_len,
9797- },
9898- )
9999- .unwrap();
100100- let mut photos_left = photos_to_upload.len();
9292+ window
9393+ .emit(
9494+ "photos-upload-meta",
9595+ PhotoUploadMeta {
9696+ photos_uploading: photos_to_upload.len(),
9797+ photos_total: photos_len,
9898+ },
9999+ )
100100+ .unwrap();
101101102102- let client = reqwest::blocking::Client::new();
102102+ let mut photos_left = photos_to_upload.len();
103103104104- loop {
105105- match photos_to_upload.pop() {
106106- Some(photo) => {
107107- let folder_name = photo.clone().replace("VRChat_", "");
108108- let mut folder_name = folder_name.split("-");
109109- let folder_name = format!(
110110- "{}-{}",
111111- folder_name.nth(0).unwrap(),
112112- folder_name.nth(0).unwrap()
113113- );
104104+ let client = reqwest::blocking::Client::new();
114105115115- let full_path = format!("{}\\{}\\{}", path.to_str().unwrap(), folder_name, photo);
116116- let file = fs::File::open(full_path);
106106+ loop {
107107+ match photos_to_upload.pop() {
108108+ Some(photo) => {
109109+ let folder_name = photo.clone().replace("VRChat_", "");
110110+ let mut folder_name = folder_name.split("-");
111111+ let folder_name = format!(
112112+ "{}-{}",
113113+ folder_name.nth(0).unwrap(),
114114+ folder_name.nth(0).unwrap()
115115+ );
117116118118- match file {
119119- Ok(file) => {
120120- let res = client
121121- .put(format!(
122122- "https://photos-cdn.phazed.xyz/api/v1/photos?token={}",
123123- &token
124124- ))
125125- .header("Content-Type", "image/png")
126126- .header("filename", photo)
127127- .body(file)
128128- .timeout(Duration::from_secs(120))
129129- .send()
130130- .unwrap()
131131- .text()
132132- .unwrap();
117117+ let full_path = format!("{}\\{}\\{}", path.to_str().unwrap(), folder_name, photo);
118118+ let file = fs::File::open(full_path);
133119134134- let res: Result<Value, Error> = serde_json::from_str(&res);
120120+ match file {
121121+ Ok(file) => {
122122+ let res = client
123123+ .put(format!(
124124+ "https://photos-cdn.phazed.xyz/api/v1/photos?token={}",
125125+ &token
126126+ ))
127127+ .header("Content-Type", "image/png")
128128+ .header("filename", photo)
129129+ .body(file)
130130+ .timeout(Duration::from_secs(120))
131131+ .send()
132132+ .unwrap()
133133+ .text()
134134+ .unwrap();
135135136136- match res {
137137- Ok(res) => {
138138- if !res["ok"].as_bool().unwrap() {
139139- println!(
140140- "Failed to upload: {}",
141141- res["error"].as_str().unwrap()
142142- );
143143- window
144144- .emit("sync-failed", res["error"].as_str().unwrap())
145145- .unwrap();
146146- break;
147147- }
148148- }
149149- Err(err) => {
150150- dbg!(err);
151151- }
152152- }
136136+ let res: Result<Value, Error> = serde_json::from_str(&res);
137137+138138+ match res {
139139+ Ok(res) => {
140140+ if !res["ok"].as_bool().unwrap() {
141141+ println!(
142142+ "Failed to upload: {}",
143143+ res["error"].as_str().unwrap()
144144+ );
145145+146146+ window
147147+ .emit("sync-failed", res["error"].as_str().unwrap())
148148+ .unwrap();
149149+150150+ break;
153151 }
154154- Err(_) => {}
152152+ }
153153+ Err(err) => {
154154+ dbg!(err);
155155+ }
155156 }
156156-157157- photos_left -= 1;
158158- window
159159- .emit(
160160- "photos-upload-meta",
161161- PhotoUploadMeta {
162162- photos_uploading: photos_left,
163163- photos_total: photos_len,
164164- },
165165- )
166166- .unwrap();
167157 }
168168- None => {
169169- break;
170170- }
158158+ Err(_) => {}
171159 }
160160+161161+ photos_left -= 1;
162162+ window
163163+ .emit(
164164+ "photos-upload-meta",
165165+ PhotoUploadMeta {
166166+ photos_uploading: photos_left,
167167+ photos_total: photos_len,
168168+ },
169169+ )
170170+ .unwrap();
171171+ }
172172+ None => {
173173+ break;
174174+ }
172175 }
176176+ }
173177174174- println!("Finished Uploading.");
175175- let mut photos_to_download: Vec<String> = Vec::new();
178178+ println!("Finished Uploading.");
179179+ let mut photos_to_download: Vec<String> = Vec::new();
176180177177- for photo in uploaded_photos {
178178- let mut found_photo = false;
179179- let photo = photo.as_str().unwrap().to_string();
180180-181181- for uploaded_photo in &photos {
182182- if &photo == uploaded_photo {
183183- found_photo = true;
184184- break;
185185- }
186186- }
181181+ for photo in uploaded_photos {
182182+ let mut found_photo = false;
183183+ let photo = photo.as_str().unwrap().to_string();
187184188188- if !found_photo {
189189- photos_to_download.push(photo);
190190- }
185185+ for uploaded_photo in &photos {
186186+ if &photo == uploaded_photo {
187187+ found_photo = true;
188188+ break;
189189+ }
191190 }
192191193193- photos_to_download.reverse();
192192+ if !found_photo {
193193+ photos_to_download.push(photo);
194194+ }
195195+ }
194196195195- let photos_len = photos_to_download.len();
196196- let mut photos_left = photos_to_download.len();
197197-198198- loop {
199199- match photos_to_download.pop() {
200200- Some(photo) => {
201201- let folder_name = photo.clone().replace("VRChat_", "");
202202- let mut folder_name = folder_name.split("-");
203203- let folder_name = format!(
204204- "{}-{}",
205205- folder_name.nth(0).unwrap(),
206206- folder_name.nth(0).unwrap()
207207- );
197197+ photos_to_download.reverse();
208198209209- let full_path = format!("{}\\{}\\{}", path.to_str().unwrap(), folder_name, photo);
199199+ let photos_len = photos_to_download.len();
200200+ let mut photos_left = photos_to_download.len();
210201211211- let res = client
212212- .get(format!(
213213- "https://photos-cdn.phazed.xyz/api/v1/photos?token={}&photo={}",
214214- &token, &photo
215215- ))
216216- .timeout(Duration::from_secs(120))
217217- .send()
218218- .unwrap()
219219- .bytes();
202202+ loop {
203203+ match photos_to_download.pop() {
204204+ Some(photo) => {
205205+ let folder_name = photo.clone().replace("VRChat_", "");
206206+ let mut folder_name = folder_name.split("-");
207207+ let folder_name = format!(
208208+ "{}-{}",
209209+ folder_name.nth(0).unwrap(),
210210+ folder_name.nth(0).unwrap()
211211+ );
220212221221- match res {
222222- Ok(res) => {
223223- let folder_path = format!("{}\\{}", path.to_str().unwrap(), folder_name);
224224- match fs::metadata(&folder_path) {
225225- Ok(_) => {}
226226- Err(_) => {
227227- fs::create_dir(folder_path).unwrap();
228228- }
229229- }
213213+ let full_path = format!("{}\\{}\\{}", path.to_str().unwrap(), folder_name, photo);
230214231231- let mut file = fs::File::create(full_path).unwrap();
232232- file.write_all(&res).unwrap();
233233- }
234234- Err(err) => {
235235- dbg!(err);
236236- }
237237- }
215215+ let res = client
216216+ .get(format!(
217217+ "https://photos-cdn.phazed.xyz/api/v1/photos?token={}&photo={}",
218218+ &token, &photo
219219+ ))
220220+ .timeout(Duration::from_secs(120))
221221+ .send()
222222+ .unwrap()
223223+ .bytes();
238224239239- photos_left -= 1;
240240- window
241241- .emit(
242242- "photos-download-meta",
243243- PhotoUploadMeta {
244244- photos_uploading: photos_left,
245245- photos_total: photos_len,
246246- },
247247- )
248248- .unwrap();
225225+ match res {
226226+ Ok(res) => {
227227+ let folder_path = format!("{}\\{}", path.to_str().unwrap(), folder_name);
228228+ match fs::metadata(&folder_path) {
229229+ Ok(_) => {}
230230+ Err(_) => {
231231+ fs::create_dir(folder_path).unwrap();
232232+ }
249233 }
250250- None => {
251251- break;
252252- }
234234+235235+ let mut file = fs::File::create(full_path).unwrap();
236236+ file.write_all(&res).unwrap();
237237+ }
238238+ Err(err) => {
239239+ dbg!(err);
240240+ }
253241 }
242242+243243+ photos_left -= 1;
244244+ window
245245+ .emit(
246246+ "photos-download-meta",
247247+ PhotoUploadMeta {
248248+ photos_uploading: photos_left,
249249+ photos_total: photos_len,
250250+ },
251251+ )
252252+ .unwrap();
253253+ }
254254+ None => {
255255+ break;
256256+ }
254257 }
258258+ }
255259256256- println!("Finished Downloading.");
260260+ println!("Finished Downloading.");
257261258258- fs::remove_file(&sync_lock_path).unwrap();
259259- window.emit("sync-finished", "h").unwrap();
262262+ fs::remove_file(&sync_lock_path).unwrap();
263263+ window.emit("sync-finished", "h").unwrap();
260264}