backup/
lib.rs

1// SPDX-FileCopyrightText: 2025 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4mod error;
5pub mod messages;
6
7use std::time::{Duration, SystemTime};
8
9use server::{AsScalar, CheckedConn, CheckedPermissions, FromScalar, MessageAllowed, Server, ServerContext};
10
11pub use crate::error::Error;
12use crate::messages::*;
13
14/// folder that will be ignored when creating backups
15pub const DO_NOT_BACKUP_FOLDER: &str = "no_backup";
16
17#[macro_export]
18macro_rules! use_api {
19    () => {
20        mod backup_permissions {
21            use backup::messages::*;
22            #[derive(Clone, Default, server::Permissions)]
23            #[server_name = "os/backup"]
24            pub struct BackupPermissions;
25        }
26        type BackupApi = backup::BackupApi<backup_permissions::BackupPermissions>;
27    };
28}
29
30#[derive(Default)]
31pub struct BackupApi<P: CheckedPermissions> {
32    conn: CheckedConn<P>,
33}
34
35impl<P: CheckedPermissions> BackupApi<P> {
36    pub fn create_backup(&self) -> Result<(), Error>
37    where
38        P: MessageAllowed<CreateBackup>,
39    {
40        self.conn.send_blocking_archive(CreateBackup)
41    }
42
43    pub fn create_backup_file(&self, backup_path: String, location: fs::Location) -> Result<(), Error>
44    where
45        P: MessageAllowed<CreateBackupFile>,
46    {
47        self.conn.send_blocking_archive(CreateBackupFile { backup_path, location })
48    }
49
50    pub fn restore_backup(&self, backup_path: String, location: fs::Location) -> Result<(), Error>
51    where
52        P: MessageAllowed<RestoreBackup>,
53    {
54        self.conn.send_blocking_archive(RestoreBackup { backup_path, location })
55    }
56
57    pub fn subscribe_restore_progress<S>(&self, context: &mut ServerContext<S>)
58    where
59        P: MessageAllowed<SubscribeRestoreProgress>,
60        S: Server + server::ArchiveEventHandler<RestoreProgress>,
61    {
62        self.conn.subscribe_archive_infallible(SubscribeRestoreProgress, context);
63    }
64}
65
66#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
67pub struct Status {
68    pub last_backup_at: Option<SystemTime>,
69    pub publish_failed: bool,
70}
71
72impl FromScalar<3> for Status {
73    fn from_scalar([last_backed_up_at_h, last_backed_up_at_l, publish_failed]: [u32; 3]) -> Self {
74        let last_backup_at = if last_backed_up_at_h != 0 || last_backed_up_at_l != 0 {
75            let h = last_backed_up_at_h.to_le_bytes();
76            let l = last_backed_up_at_l.to_le_bytes();
77            let last_backed_up_at_u64 = u64::from_le_bytes([h[0], h[1], h[2], h[3], l[0], l[1], l[2], l[3]]);
78            Some(SystemTime::UNIX_EPOCH + Duration::from_secs(last_backed_up_at_u64))
79        } else {
80            None
81        };
82
83        Status { last_backup_at, publish_failed: publish_failed != 0 }
84    }
85}
86
87impl AsScalar<3> for Status {
88    fn as_scalar(&self) -> [u32; 3] {
89        let (last_backed_up_at_h, last_backed_up_at_l) = if let Some(last_backup_at) = self.last_backup_at {
90            let [h1, h2, h3, h4, l1, l2, l3, l4] =
91                last_backup_at.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs().to_le_bytes();
92            (u32::from_le_bytes([h1, h2, h3, h4]), u32::from_le_bytes([l1, l2, l3, l4]))
93        } else {
94            (0, 0)
95        };
96
97        [last_backed_up_at_h, last_backed_up_at_l, u32::from(self.publish_failed)]
98    }
99}