[READ-ONLY] Mirror of https://github.com/probablykasper/time-machine-inspector. Time Machine backup size inspector app
backup macos tauri time-machine
0

Configure Feed

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

Support multiple backups loaded at once

Kasper (Jan 16, 2022, 12:37 PM +0100) cca38d40 8f9e343a

+111 -27
+40 -1
src-tauri/src/cmd.rs
··· 1 1 use crate::dir_map::DirMap; 2 2 use crate::{compare, listbackups, throw}; 3 + use std::collections::HashMap; 3 4 use std::fs::File; 4 5 use std::process::ExitStatus; 5 6 use std::sync::{Mutex, MutexGuard}; ··· 88 89 Ok(dir_map) 89 90 } 90 91 92 + #[derive(Default)] 93 + pub struct BackupDirMaps(pub Mutex<HashMap<(String, String), DirMap<u64>>>); 94 + 95 + impl BackupDirMaps { 96 + pub fn lock(&self) -> Result<MutexGuard<HashMap<(String, String), DirMap<u64>>>, String> { 97 + match self.0.lock() { 98 + Ok(mutex) => Ok(mutex), 99 + Err(e) => throw!("Unable to lock backup list: {}", e), 100 + } 101 + } 102 + } 103 + 91 104 #[command] 92 105 pub async fn compare_backups<'a>( 93 106 old: String, 94 107 new: String, 108 + refresh: bool, 95 109 w: Window, 110 + state: State<'_, BackupDirMaps>, 96 111 ) -> Result<DirMap<u64>, String> { 112 + let old_new = (old.clone(), new.clone()); 113 + 114 + // get cached dir_map 115 + if !refresh { 116 + let dir_maps = state.lock()?; 117 + match dir_maps.get(&old_new) { 118 + Some(dir_map) => { 119 + return Ok(dir_map.clone()); 120 + } 121 + None => {} 122 + } 123 + } 124 + 97 125 full_disk_access(w).await?; 98 126 let dir_map = compare::compare(&old, &new)?; 99 - Ok(dir_map) 127 + state.lock()?.insert(old_new, dir_map.clone()); 128 + 129 + Ok(dir_map.clone()) 130 + } 131 + 132 + #[command] 133 + pub async fn cached_backups( 134 + state: State<'_, BackupDirMaps>, 135 + ) -> Result<Vec<(String, String)>, String> { 136 + let dir_maps = state.lock()?; 137 + let cached_paths = dir_maps.keys().map(|t| t.clone()).collect(); 138 + Ok(cached_paths) 100 139 }
+2
src-tauri/src/main.rs
··· 42 42 43 43 tauri::Builder::default() 44 44 .manage(cmd::BackupList(Default::default())) 45 + .manage(cmd::BackupDirMaps(Default::default())) 45 46 .invoke_handler(tauri::generate_handler![ 46 47 error_popup, 47 48 cmd::load_backups, 48 49 cmd::compare_backups, 50 + cmd::cached_backups, 49 51 ]) 50 52 .create_window("main", WindowUrl::default(), |win, webview| { 51 53 let win = win
+9 -4
src/Item.svelte
··· 11 11 12 12 <script lang="ts"> 13 13 import { createEventDispatcher } from 'svelte' 14 + import { cachedBackups } from './page' 14 15 import type { DirMap } from './page' 15 16 16 17 export let map: DirMap ··· 19 20 export let dir: string 20 21 export let fullPath: string 21 22 $: isFolder = map[fullPath] !== undefined 23 + $: isCached = $cachedBackups.includes(fullPath) 22 24 23 25 export let selectedPath: string 24 26 export let open = true ··· 43 45 <div 44 46 class="item" 45 47 class:open 48 + class:is-cached={isCached} 46 49 on:click={itemClick} 47 50 style={`padding-left: ${14 * indentLevel + 10}px`} 48 51 class:selected={selectedPath === fullPath + '/' + name}> ··· 76 79 padding: 4px 0px 77 80 box-sizing: border-box 78 81 width: 100% 79 - &.selected 80 - background-color: hsla(216, 70%, 70%, 0.2) 81 - &.open svg 82 - transform: rotate(90deg) 82 + .selected 83 + background-color: hsla(216, 70%, 70%, 0.2) 84 + .open svg 85 + transform: rotate(90deg) 86 + .is-cached 87 + font-weight: 600 83 88 svg 84 89 fill: hsla(216, 50%, 70%, 0.75) 85 90 width: 10px
+14 -6
src/Page.svelte
··· 1 1 <script lang="ts"> 2 2 import { runCmd } from './general' 3 3 import PageItem, { ItemClickEvent } from './PageItem.svelte' 4 - import { backups, page } from './page' 4 + import { backups, page, loadCachedBackups, cachedBackups } from './page' 5 5 6 6 let selectedPath = '' 7 7 ··· 14 14 let dirMap: DirMap | null = null 15 15 16 16 let loading = false 17 - async function compare() { 17 + async function compare(autoLoad = false) { 18 18 if (loading || $backups === null) { 19 19 return 20 20 } 21 21 const fullPathParent = $page.fullPath.substring(0, $page.fullPath.lastIndexOf('/')) 22 22 const dir = $backups.dirs[fullPathParent][$page.name] 23 23 if (dir !== undefined) { 24 - loading = true 24 + if (!autoLoad) { 25 + loading = true 26 + } 25 27 const result = (await runCmd('compare_backups', { 26 28 old: $page.prevPath, 27 29 new: $page.fullPath, 28 - })) as { map: DirMap } 30 + refresh: false, 31 + })) as { map: DirMap; cached_paths: [string, string][] } 29 32 dirMap = result.map 30 - console.log(dirMap) 33 + loadCachedBackups() 34 + console.log(result) 31 35 } 32 36 loading = false 37 + } 38 + 39 + // auto load 40 + $: if ($cachedBackups.includes($page.fullPath)) { 41 + compare(true) 33 42 } 34 43 35 44 function itemClick(e: ItemClickEvent) { ··· 45 54 {#if $page.fullPath === ''} 46 55 <main class="empty"> 47 56 <p>You can open a backup from the sidebar when it's loaded</p> 48 - <button on:click={compare}>Load</button> 49 57 </main> 50 58 {:else} 51 59 <main>
+10 -10
src/PageItem.svelte
··· 56 56 {/if} 57 57 {name} 58 58 <div class="size"> 59 - {#if size < 1_000} 59 + {#if size < 1000} 60 60 {size} 61 - {:else if size < 1_000_000} 62 - {size / 1_000} KB 63 - {:else if size < 1_000_000_000} 64 - {size / 1_000_000} MB 65 - {:else if size < 1_000_000_000_000} 66 - {size / 1_000_000_000} GB 67 - {:else if size < 1_000_000_000_000_000} 68 - {size / 1_000_000_000_000} TB 61 + {:else if size < 1000000} 62 + {size / 1000} KB 63 + {:else if size < 1000000000} 64 + {size / 1000000} MB 65 + {:else if size < 1000000000000} 66 + {size / 1000000000} GB 67 + {:else if size < 1000000000000000} 68 + {size / 1000000000000} TB 69 69 {/if} 70 70 </div> 71 71 </div> ··· 105 105 fill: hsla(216, 50%, 70%, 0.75) 106 106 width: 10px 107 107 height: 10px 108 - margin-right: 1px 108 + margin-right: 4px 109 109 .size 110 110 display: inline-block 111 111 margin-left: auto
+36 -6
src/page.ts
··· 13 13 [name: string]: null 14 14 } 15 15 16 - export const backups = writable<Backups | null>(null) 16 + export const backups = (() => { 17 + const store = writable<Backups | null>(null) 18 + return { 19 + subscribe: store.subscribe, 20 + set: (value: Backups | null) => { 21 + store.set(value) 22 + loadCachedBackups() 23 + }, 24 + } 25 + })() 17 26 18 - export const page = writable({ 19 - fullPath: '', 20 - name: '', 21 - prevPath: '', 22 - }) 27 + export const cachedBackups = writable([] as string[]) 28 + export async function loadCachedBackups() { 29 + const result = (await runCmd('cached_backups')) as [string, string][] 30 + const newOnly = result.map((b) => b[1]) 31 + cachedBackups.set(newOnly) 32 + } 33 + 34 + type Page = { 35 + fullPath: string 36 + name: string 37 + prevPath: string 38 + } 39 + export const page = (() => { 40 + const store = writable<Page>({ 41 + fullPath: '', 42 + name: '', 43 + prevPath: '', 44 + }) 45 + return { 46 + subscribe: store.subscribe, 47 + set: (value: Page) => { 48 + store.set(value) 49 + loadCachedBackups() 50 + }, 51 + } 52 + })() 23 53 24 54 export function close() { 25 55 page.set({