[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.

Refactor page items

Kasper (Jan 28, 2022, 12:07 AM +0100) 468a32ee 0403be26

+174 -156
+8 -3
src-tauri/src/cmd.rs
··· 91 91 } 92 92 93 93 #[derive(Serialize, Clone)] 94 + pub struct LoadedBackupItem { 95 + pub size: u64, 96 + } 97 + 98 + #[derive(Serialize, Clone)] 94 99 pub struct LoadedBackup { 95 100 pub old: String, 96 101 pub new: String, 97 - pub map: DirMap<u64>, 102 + pub map: DirMap<LoadedBackupItem>, 98 103 pub loading: bool, 99 104 } 100 105 pub type LoadedBackupsMap = HashMap<(String, String), LoadedBackup>; ··· 129 134 Ok(info.collect()) 130 135 } 131 136 132 - async fn do_compare(old: &str, new: &str, w: Window) -> Result<DirMap<u64>, String> { 137 + async fn do_compare(old: &str, new: &str, w: Window) -> Result<DirMap<LoadedBackupItem>, String> { 133 138 full_disk_access(w).await?; 134 139 Ok(compare::compare(&old, &new)?) 135 140 } ··· 141 146 refresh: bool, 142 147 w: Window, 143 148 state: State<'_, LoadedBackups>, 144 - ) -> Result<DirMap<u64>, String> { 149 + ) -> Result<DirMap<LoadedBackupItem>, String> { 145 150 let old_new = (old.clone(), new.clone()); 146 151 147 152 // get cached dir_map
+2 -2
src-tauri/src/compare.rs
··· 1 - use crate::cmd::check_cmd_success; 1 + use crate::cmd::{check_cmd_success, LoadedBackupItem}; 2 2 use crate::dir_map::DirMap; 3 3 use crate::{reset_dur, throw}; 4 4 use plist::Value; ··· 122 122 Ok(change) 123 123 } 124 124 125 - pub fn compare(old: &str, new: &str) -> Result<DirMap<u64>, String> { 125 + pub fn compare(old: &str, new: &str) -> Result<DirMap<LoadedBackupItem>, String> { 126 126 let mut anchor = Instant::now(); 127 127 128 128 let mut cmd = Command::new("tmutil")
+6 -3
src-tauri/src/dir_map.rs
··· 1 + use crate::cmd::LoadedBackupItem; 1 2 use crate::{compare, throw}; 2 3 use serde::Serialize; 3 4 use std::collections::hash_map::Entry; ··· 61 62 } 62 63 } 63 64 64 - impl DirMap<u64> { 65 + impl DirMap<LoadedBackupItem> { 65 66 pub fn from_comparison(comparison: compare::Comparison) -> Result<Self, String> { 66 67 let mut dir_map = DirMap::new(); 67 68 ··· 77 78 if ancestor == Path::new("/") { 78 79 break; 79 80 } 80 - let item = dir_map.item_entry(ancestor)?.or_insert(0); 81 - *item += new_item.size; 81 + let item = dir_map 82 + .item_entry(ancestor)? 83 + .or_insert(LoadedBackupItem { size: 0 }); 84 + item.size += new_item.size; 82 85 } 83 86 } 84 87 Ok(dir_map)
+26 -34
src/page/Page.svelte
··· 1 1 <script lang="ts"> 2 2 import { runCmd } from '../general' 3 - import PageItem, { ItemClickEvent } from './PageItem.svelte' 4 - import { backups, page, backupInfos } from './page' 3 + import PageItems from './PageItems.svelte' 4 + import { backups, page, backupInfos, pageMap, PageMap } from './page' 5 5 import Button from '../lib/Button.svelte' 6 6 import ProgressBar from '../lib/ProgressBar.svelte' 7 - 8 - let selectedPath = '' 9 - 10 - type DirItem = { 11 - [key: string]: number 12 - } 13 - type DirMap = { 14 - [key: string]: DirItem 15 - } 16 - let dirMap: DirMap | null = null 17 7 18 8 async function compare(autoLoad = false) { 19 9 if ($page.loading || $backups === null) { ··· 29 19 old: $page.prevPath, 30 20 new: $page.fullPath, 31 21 refresh: false, 32 - })) as { map: DirMap; cached_paths: [string, string][] } 33 - dirMap = result.map 22 + })) as { map: PageMap; cached_paths: [string, string][] } 23 + $pageMap = result.map 34 24 backupInfos.load() 35 25 console.log(result) 36 26 } 37 27 } 38 28 29 + // $: pageItems = getPageItems(dirMap) 30 + // function getPageItems(dirMap: DirMap | null) { 31 + // if (dirMap === null) { 32 + // return null 33 + // } 34 + // const items = {} 35 + // for (const [dir, items] of Object.entries(dirMap)) { 36 + // console.log(dir, items) 37 + // } 38 + // return items 39 + // // for (const [path, items] of Object.entries(dirMap)) { 40 + // // const items = {} 41 + // // for (const item of dirs) { 42 + // // items.push({ 43 + // // size: item, 44 + // // }) 45 + // // } 46 + // // } 47 + // } 48 + 39 49 $: autoLoad($page.prevPath, $page.fullPath) 40 50 function autoLoad(oldPath: string, newPath: string) { 41 51 for (const info of $backupInfos) { ··· 44 54 } 45 55 } 46 56 } 47 - 48 - function itemClick(e: ItemClickEvent) { 49 - if (e.detail.isFolder) { 50 - e.detail.toggleChildren() 51 - } 52 - selectedPath = e.detail.fullPath + '/' + e.detail.name 53 - } 54 - 55 - $: rootPath = $page.fullPath 56 57 </script> 57 58 58 59 {#if $page.fullPath === ''} ··· 67 68 <div class="absolute center-align"> 68 69 <ProgressBar /> 69 70 </div> 70 - {:else if dirMap === null || dirMap[rootPath] === undefined} 71 + {:else if $pageMap === null || $pageMap[$page.fullPath] === undefined} 71 72 <div class="absolute center-align"> 72 73 <Button on:click={() => compare()}>Load</Button> 73 74 </div> 74 75 {:else} 75 - {#each Object.entries(dirMap[rootPath]) as [childName, size]} 76 - <PageItem 77 - map={dirMap} 78 - {selectedPath} 79 - name={childName} 80 - {size} 81 - fullPath={rootPath + '/' + childName} 82 - on:click={itemClick} 83 - /> 84 - {/each} 76 + <PageItems path={$page.fullPath} /> 85 77 {/if} 86 78 </div> 87 79 </main>
-113
src/page/PageItem.svelte
··· 1 - <script lang="ts" context="module"> 2 - export type ItemClickEventDetail = { 3 - name: string 4 - fullPath: string 5 - isFolder: boolean 6 - toggleChildren: () => void 7 - } 8 - export type ItemClickEvent = CustomEvent<ItemClickEventDetail> 9 - </script> 10 - 11 - <script lang="ts"> 12 - import { createEventDispatcher } from 'svelte' 13 - 14 - type DirItem = { 15 - [key: string]: number 16 - } 17 - type DirMap = { 18 - [key: string]: DirItem 19 - } 20 - 21 - export let map: DirMap 22 - 23 - export let name: string 24 - export let fullPath: string 25 - export let size: number 26 - $: isFolder = map[fullPath] !== undefined 27 - 28 - export let selectedPath: string 29 - export let open = false 30 - 31 - export let indentLevel = 0 32 - 33 - const dispatch = createEventDispatcher<{ click: ItemClickEventDetail }>() 34 - 35 - function itemClick() { 36 - dispatch('click', { 37 - name, 38 - fullPath, 39 - isFolder, 40 - toggleChildren: () => { 41 - open = !open 42 - }, 43 - }) 44 - } 45 - </script> 46 - 47 - <div 48 - class="item" 49 - class:open 50 - on:click={itemClick} 51 - style={`padding-left: ${14 * indentLevel + 10}px`} 52 - class:selected={selectedPath === fullPath + '/' + name}> 53 - {#if isFolder} 54 - <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" 55 - ><path d="M21 12l-18 12v-24z" /></svg> 56 - {/if} 57 - {name} 58 - <div class="size"> 59 - {#if size < 1000} 60 - {size} 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 - {/if} 70 - </div> 71 - </div> 72 - <div class="children"> 73 - {#if open && isFolder} 74 - {#each Object.entries(map[fullPath]) as [childName, size]} 75 - <svelte:self 76 - {map} 77 - {selectedPath} 78 - name={childName} 79 - {size} 80 - fullPath={fullPath + '/' + childName} 81 - on:click 82 - indentLevel={indentLevel + 1} /> 83 - {/each} 84 - {/if} 85 - </div> 86 - 87 - <style lang="sass"> 88 - .item 89 - font-size: 14px 90 - display: flex 91 - align-items: center 92 - box-sizing: border-box 93 - color: hsla(216, 50%, 70%, 0.75) 94 - cursor: default 95 - user-select: none 96 - padding-top: 4px 97 - padding-bottom: 4px 98 - box-sizing: border-box 99 - width: 100% 100 - &.selected 101 - background-color: hsla(216, 70%, 70%, 0.2) 102 - &.open svg 103 - transform: rotate(90deg) 104 - svg 105 - fill: hsla(216, 50%, 70%, 0.75) 106 - width: 10px 107 - height: 10px 108 - margin-right: 4px 109 - .size 110 - display: inline-block 111 - margin-left: auto 112 - margin-right: 10px 113 - </style>
+116
src/page/PageItems.svelte
··· 1 + <script lang="ts"> 2 + import { PageMap, pageMap, selectedPath } from './page' 3 + 4 + export let path: string 5 + 6 + type Item = { 7 + name: string 8 + size: number 9 + path: string 10 + isFolder: boolean 11 + isOpen: boolean 12 + } 13 + 14 + function getChildPath(path: string, childPath: string) { 15 + if (path === '/') { 16 + return '/' + childPath 17 + } else { 18 + return path + '/' + childPath 19 + } 20 + } 21 + 22 + $: dir = getDir($pageMap, path) 23 + function getDir(map: PageMap | null, path: string): Item[] { 24 + if (map === null) { 25 + return [] 26 + } 27 + const names = Object.keys(map[path]).sort() 28 + return names.map((name) => { 29 + const childPath = getChildPath(path, name) 30 + const item: Item = { 31 + name, 32 + size: map[path][name].size, 33 + path: childPath, 34 + isFolder: map[childPath] !== undefined, 35 + isOpen: !!map[path][name].isOpen, 36 + } 37 + return item 38 + }) 39 + } 40 + 41 + function openOrClose(name: string) { 42 + $pageMap[path][name].isOpen = !$pageMap[path][name].isOpen 43 + } 44 + 45 + export let indentLevel = 0 46 + </script> 47 + 48 + {#each dir as item} 49 + <div 50 + class="item" 51 + class:open={item.isOpen} 52 + class:selected={$selectedPath === item.path} 53 + style={`padding-left: ${14 * indentLevel + 2}px`} 54 + on:click={() => ($selectedPath = item.path)} 55 + on:dblclick={() => openOrClose(item.name)} 56 + > 57 + {#if $pageMap && $pageMap[item.path]} 58 + <div class="arrow" on:click|stopPropagation={() => openOrClose(item.name)}> 59 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" 60 + ><path d="M21 12l-18 12v-24z" /></svg 61 + > 62 + </div> 63 + {/if} 64 + {item.name} 65 + <div class="size"> 66 + {#if item.size < 1000} 67 + {item.size} 68 + {:else if item.size < 1000000} 69 + {item.size / 1000} KB 70 + {:else if item.size < 1000000000} 71 + {item.size / 1000000} MB 72 + {:else if item.size < 1000000000000} 73 + {item.size / 1000000000} GB 74 + {:else if item.size < 1000000000000000} 75 + {item.size / 1000000000000} TB 76 + {/if} 77 + </div> 78 + </div> 79 + <div class="children"> 80 + {#if item.isOpen} 81 + <svelte:self path={item.path} indentLevel={indentLevel + 1} /> 82 + {/if} 83 + </div> 84 + {/each} 85 + 86 + <style lang="sass"> 87 + .item 88 + font-size: 14px 89 + display: flex 90 + align-items: center 91 + box-sizing: border-box 92 + color: hsla(216, 50%, 70%, 0.75) 93 + cursor: default 94 + user-select: none 95 + padding-top: 4px 96 + padding-bottom: 4px 97 + box-sizing: border-box 98 + width: 100% 99 + &.selected 100 + background-color: hsla(216, 70%, 70%, 0.2) 101 + &.open svg 102 + transform: rotate(90deg) 103 + .arrow 104 + padding: 5px 105 + margin-right: 4px 106 + width: 10px 107 + height: 10px 108 + display: flex 109 + align-items: center 110 + svg 111 + fill: hsla(216, 50%, 70%, 0.75) 112 + .size 113 + display: inline-block 114 + margin-left: auto 115 + margin-right: 10px 116 + </style>
+15
src/page/page.ts
··· 48 48 } 49 49 })() 50 50 51 + export const selectedPath = writable(null as string | null) 52 + 53 + export type PageMap = { 54 + [name: string]: PageItems 55 + } 56 + export type PageItems = { 57 + [name: string]: PageItem 58 + } 59 + export type PageItem = { 60 + size: number 61 + isOpen?: boolean 62 + } 63 + 64 + export const pageMap = writable({} as PageMap) 65 + 51 66 type Page = { 52 67 fullPath: string 53 68 name: string
+1 -1
src/sidebar/SidebarItems.svelte
··· 23 23 24 24 $: dir = getDir(backups, path) 25 25 function getDir(backups: Backups, path: string): Item[] { 26 - let names = Object.keys(backups.dirs[path]).sort() 26 + const names = Object.keys(backups.dirs[path]).sort() 27 27 let prevChildPath = null as string | null 28 28 return names.map((name) => { 29 29 const childPath = getChildPath(path, name)