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

Load TM backups

Kasper (Jan 2, 2022, 6:49 AM +0100) 46c8206c e18a48ca

+151 -9
+62
src-tauri/src/cmd.rs
··· 1 + use crate::throw; 2 + use std::fs::File; 3 + use std::process::Command; 4 + use tauri::api::{dialog, shell}; 5 + use tauri::{command, Window}; 6 + 7 + fn parse_output(bytes: Vec<u8>) -> Result<String, String> { 8 + match String::from_utf8(bytes) { 9 + Ok(s) => Ok(s), 10 + Err(e) => throw!("Unable to parse output: {}", e), 11 + } 12 + } 13 + 14 + fn code_to_str(code: Option<i32>) -> String { 15 + match code { 16 + Some(code) => format!("{}", code), 17 + None => "None".to_string(), 18 + } 19 + } 20 + 21 + #[command] 22 + pub async fn load_backups(w: Window) -> Result<Option<String>, String> { 23 + match File::open("/Library/Preferences/com.apple.TimeMachine.plist") { 24 + Ok(_file) => {} 25 + Err(e) => match e.kind() { 26 + std::io::ErrorKind::PermissionDenied => { 27 + dialog::message( 28 + Some(&w), 29 + "Full Disk Access", 30 + "Time Machine Utility requires full disk access to interact with Time Machine.\n\ 31 + To grant access:\n\ 32 + \n\ 33 + 1. Go to System Preferences > Security & Privacy > Privacy\n\ 34 + 2. Select Full Disk Access on the left\n\ 35 + 3. Add and enable Time Machine Inspector on the right", 36 + ); 37 + let link = "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"; 38 + shell::open(link.to_string(), None).unwrap(); 39 + 40 + return Ok(None); 41 + } 42 + _ => eprintln!("Unable to open Time Machine preferences: {}", e), 43 + }, 44 + }; 45 + 46 + let cmd = Command::new("tmutil") 47 + .arg("listbackups") 48 + .output() 49 + .expect("Error getting backups"); 50 + 51 + if !cmd.status.success() { 52 + let stderr = parse_output(cmd.stderr)?; 53 + eprintln!( 54 + "listbackups exited with error code {}. stderr:\n{}", 55 + code_to_str(cmd.status.code()), 56 + stderr, 57 + ); 58 + throw!("{}", stderr); 59 + } 60 + 61 + Ok(Some(parse_output(cmd.stdout)?)) 62 + }
+24 -3
src-tauri/src/main.rs
··· 3 3 windows_subsystem = "windows" 4 4 )] 5 5 6 - use tauri::api::shell; 7 - use tauri::{CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu, WindowBuilder, WindowUrl}; 6 + use std::thread; 7 + use tauri::api::{dialog, shell}; 8 + use tauri::{ 9 + command, CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu, Window, WindowBuilder, WindowUrl, 10 + }; 11 + 12 + mod cmd; 13 + 14 + #[command] 15 + fn error_popup(msg: String, win: Window) { 16 + println!("Error: {}", msg); 17 + thread::spawn(move || { 18 + dialog::message(Some(&win), "Error", msg); 19 + }); 20 + } 21 + 22 + #[macro_export] 23 + macro_rules! throw { 24 + ($($arg:tt)*) => {{ 25 + return Err(format!($($arg)*)) 26 + }}; 27 + } 8 28 9 29 fn main() { 10 30 let ctx = tauri::generate_context!(); 11 31 12 32 tauri::Builder::default() 13 - .invoke_handler(tauri::generate_handler![]) 33 + .invoke_handler(tauri::generate_handler![error_popup, cmd::load_backups]) 14 34 .create_window("main", WindowUrl::default(), |win, webview| { 15 35 let win = win 16 36 .title("Time Machine Inspector") ··· 25 45 return (win, webview); 26 46 }) 27 47 .menu(Menu::with_items([ 48 + #[cfg(target_os = "macos")] 28 49 MenuEntry::Submenu(Submenu::new( 29 50 &ctx.package_info().name, 30 51 Menu::with_items([
+1 -1
src-tauri/tauri.conf.json
··· 25 25 "resources": [], 26 26 "externalBin": [], 27 27 "copyright": "© 2021 kasper.space", 28 - "category": "DeveloperTool", 28 + "category": "Utility", 29 29 "shortDescription": "", 30 30 "longDescription": "", 31 31 "deb": {
+54 -5
src/App.svelte
··· 1 1 <script lang="ts"> 2 + import { runCmd } from './lib/general' 3 + 4 + let backups: string[] | null = [] 5 + 6 + let loading = false 7 + async function loadBackups() { 8 + if (!loading) { 9 + loading = true 10 + const stdout: string | null = await runCmd('load_backups') 11 + if (stdout === null) { 12 + backups = null 13 + } else { 14 + backups = stdout.split('\n') 15 + } 16 + loading = false 17 + } 18 + } 2 19 </script> 3 20 4 - <h1>Time Machine Inspector</h1> 21 + <div class="button" on:click={loadBackups} class:disabled={loading} tabindex="0"> 22 + {#if loading} 23 + Loading... 24 + {:else} 25 + Load Backups 26 + {/if} 27 + </div> 28 + 29 + {#if backups !== null} 30 + {#each backups as backup} 31 + <div class="backup">{backup}</div> 32 + {/each} 33 + {/if} 5 34 6 35 <style lang="sass"> 36 + :global(html) 37 + height: 100% 38 + background-color: #0f1114 7 39 :global(body) 8 40 font-family: Arial, Helvetica, sans-serif 9 41 font-size: 18px 10 - background-color: #111318 42 + color: #f2f2f2 43 + .button 44 + display: inline-block 45 + position: relative 46 + user-select: none 47 + -webkit-user-select: none 48 + cursor: default 49 + outline: none 50 + width: 90px 51 + padding: 5px 12px 52 + margin: 10px 11 53 color: #f2f2f2 12 - text-align: center 13 - h1 14 - color: #069893 54 + border-radius: 3px 55 + font-size: 14px 56 + background-color: rgba(255,255,255,0.15) 57 + box-shadow: 0px 0px 0px 1px hsla(0, 0%, 100%, 0.3) 58 + transition: all 0.2s ease-in-out 59 + &.disabled 60 + opacity: 0.75 61 + .backup 62 + color: #ffffff 63 + font-size: 13px 15 64 </style>
+10
src/lib/general.ts
··· 1 + import { invoke } from '@tauri-apps/api/tauri' 2 + 3 + export function popup(msg: string) { 4 + invoke('error_popup', { msg }) 5 + } 6 + 7 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 + export async function runCmd<T = any>(cmd: string, options: { [key: string]: T } = {}) { 9 + return (await invoke(cmd, options).catch(popup)) as T 10 + }