app: Separate data per profile
Puts the database in a subdirectory and uses a different path for the settings.
This commit is contained in:
parent
f7dfbcceda
commit
54bd7aad84
|
@ -64,6 +64,7 @@ endif
|
|||
# GSchema
|
||||
gschema_conf = configuration_data()
|
||||
gschema_conf.set('app-id', application_id)
|
||||
gschema_conf.set('profile', profile)
|
||||
gschema_conf.set('gettext-package', gettext_package)
|
||||
configure_file(
|
||||
input: '@0@.gschema.xml.in'.format(base_id),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<schemalist>
|
||||
<schema path="/org/gnome/Fractal/" id="@app-id@" gettext-domain="@gettext-package@">
|
||||
<schema path="/org/gnome/Fractal/@profile@/" id="@app-id@" gettext-domain="@gettext-package@">
|
||||
<key name="window-width" type="i">
|
||||
<default>-1</default>
|
||||
<summary>Window width</summary>
|
||||
|
|
118
src/secret.rs
118
src/secret.rs
|
@ -1,7 +1,7 @@
|
|||
use std::{collections::HashMap, ffi::OsStr, fmt, path::PathBuf, string::FromUtf8Error};
|
||||
use std::{collections::HashMap, ffi::OsStr, fmt, fs, path::PathBuf, string::FromUtf8Error};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use log::error;
|
||||
use log::{debug, error, warn};
|
||||
use matrix_sdk::ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
|
||||
use oo7::{Item, Keyring};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -9,9 +9,14 @@ use serde_json::error::Error as JsonError;
|
|||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use crate::{config::APP_ID, gettext_f, user_facing_error::UserFacingError};
|
||||
use crate::{
|
||||
config::{APP_ID, PROFILE},
|
||||
gettext_f, spawn_tokio,
|
||||
user_facing_error::UserFacingError,
|
||||
utils::matrix,
|
||||
};
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 1;
|
||||
pub const CURRENT_VERSION: u8 = 2;
|
||||
const SCHEMA_ATTRIBUTE: &str = "xdg:schema";
|
||||
|
||||
/// Any error that can happen when interacting with the secret service.
|
||||
|
@ -36,6 +41,10 @@ pub enum SecretError {
|
|||
/// An error occurred interacting with the secret service.
|
||||
#[error(transparent)]
|
||||
Oo7(#[from] oo7::Error),
|
||||
|
||||
/// Trying to restore a session with the wrong profile.
|
||||
#[error("Session found for wrong profile")]
|
||||
WrongProfile,
|
||||
}
|
||||
|
||||
impl SecretError {
|
||||
|
@ -167,7 +176,7 @@ impl StoredSession {
|
|||
},
|
||||
None => 0,
|
||||
};
|
||||
if version > 0 && version != CURRENT_VERSION {
|
||||
if version > CURRENT_VERSION {
|
||||
return Err(SecretError::UnsupportedVersion {
|
||||
version,
|
||||
item,
|
||||
|
@ -175,6 +184,23 @@ impl StoredSession {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: Remove this and request profile in Keyring::search_items when we remove
|
||||
// migration.
|
||||
match attr.get("profile") {
|
||||
// Ignore the item if it's for another profile.
|
||||
Some(profile) if *profile != PROFILE.as_str() => return Err(SecretError::WrongProfile),
|
||||
// It's an error if the version is at least 2 but there is no profile.
|
||||
// Versions older than 2 will be migrated.
|
||||
None if version >= 2 => {
|
||||
return Err(SecretError::CorruptSession {
|
||||
error: gettext("Could not find profile in stored session"),
|
||||
item,
|
||||
});
|
||||
}
|
||||
// No issue for other cases.
|
||||
_ => {}
|
||||
};
|
||||
|
||||
let homeserver = match attr.get("homeserver") {
|
||||
Some(string) => match Url::parse(string) {
|
||||
Ok(homeserver) => homeserver,
|
||||
|
@ -273,7 +299,7 @@ impl StoredSession {
|
|||
version,
|
||||
};
|
||||
|
||||
if version == 0 {
|
||||
if version < CURRENT_VERSION {
|
||||
Err(SecretError::OldVersion { item, session })
|
||||
} else {
|
||||
Ok(session)
|
||||
|
@ -288,6 +314,7 @@ impl StoredSession {
|
|||
("device-id", self.device_id.to_string()),
|
||||
("db-path", self.path.to_str().unwrap().to_owned()),
|
||||
("version", self.version.to_string()),
|
||||
("profile", PROFILE.to_string()),
|
||||
(SCHEMA_ATTRIBUTE, APP_ID.to_owned()),
|
||||
])
|
||||
}
|
||||
|
@ -302,6 +329,79 @@ impl StoredSession {
|
|||
.and_then(OsStr::to_str)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Delete this session from the system.
|
||||
pub async fn delete(self, item: Item) {
|
||||
debug!(
|
||||
"Removing session {} found for user {} with ID {}…",
|
||||
self.version,
|
||||
self.user_id,
|
||||
self.id()
|
||||
);
|
||||
|
||||
spawn_tokio!(async move {
|
||||
match matrix::client_with_stored_session(&self).await {
|
||||
Ok(client) => {
|
||||
if let Err(error) = client.logout().await {
|
||||
error!("Failed to log out old session: {error}");
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to build client to log out old session: {error}")
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(error) = item.delete().await {
|
||||
error!("Failed to delete old session from Secret Service: {error}");
|
||||
};
|
||||
|
||||
if let Err(error) = fs::remove_dir_all(self.path) {
|
||||
error!("Failed to remove database from old session: {error}");
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Migrate this session to version 2.
|
||||
///
|
||||
/// This implies moving the database under the profile's directory.
|
||||
pub async fn migrate_to_v2(mut self, item: Item) {
|
||||
warn!(
|
||||
"Session version {} found for user {} with ID {}, migrating to version 2…",
|
||||
self.version,
|
||||
self.user_id,
|
||||
self.id()
|
||||
);
|
||||
|
||||
spawn_tokio!(async move {
|
||||
let new_path = self
|
||||
.path
|
||||
.with_file_name(format!("{}/{}", PROFILE.as_str(), self.id()));
|
||||
debug!("Moving database to: {}", new_path.to_string_lossy());
|
||||
|
||||
if let Err(error) = fs::create_dir_all(&new_path) {
|
||||
error!("Failed to create new directory: {error}");
|
||||
}
|
||||
|
||||
if let Err(error) = fs::rename(&self.path, &new_path) {
|
||||
error!("Failed to move database: {error}");
|
||||
}
|
||||
|
||||
self.path = new_path;
|
||||
self.version = 2;
|
||||
|
||||
if let Err(error) = item.delete().await {
|
||||
error!("Failed to remove outdated session: {error}");
|
||||
}
|
||||
|
||||
if let Err(error) = store_session(&self).await {
|
||||
error!("Failed to store updated session: {error}");
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// A possible error value when converting a `Secret` from a UTF-8 byte vector.
|
||||
|
@ -349,7 +449,11 @@ pub async fn restore_sessions() -> Result<Vec<StoredSession>, SecretError> {
|
|||
let mut sessions = Vec::with_capacity(items.len());
|
||||
|
||||
for item in items {
|
||||
sessions.push(StoredSession::try_from_secret_item(item).await?);
|
||||
match StoredSession::try_from_secret_item(item).await {
|
||||
Ok(session) => sessions.push(session),
|
||||
Err(SecretError::WrongProfile) => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(sessions)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cell::Cell, fs};
|
||||
use std::cell::Cell;
|
||||
|
||||
use adw::subclass::prelude::AdwApplicationWindowImpl;
|
||||
use gettextrs::gettext;
|
||||
|
@ -12,9 +12,7 @@ use crate::{
|
|||
components::Spinner,
|
||||
config::{APP_ID, PROFILE},
|
||||
secret::{self, SecretError},
|
||||
spawn, spawn_tokio,
|
||||
utils::matrix,
|
||||
Application, ErrorPage, Greeter, Login, Session,
|
||||
spawn, spawn_tokio, Application, ErrorPage, Greeter, Login, Session,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
|
@ -266,35 +264,13 @@ impl Window {
|
|||
}
|
||||
}
|
||||
Err(error) => match error {
|
||||
SecretError::OldVersion { item, session } if session.version == 0 => {
|
||||
warn!(
|
||||
"Found old session for user {} with sled store in '{}', removing…",
|
||||
session.user_id,
|
||||
session.path.to_string_lossy()
|
||||
);
|
||||
|
||||
spawn_tokio!(async move {
|
||||
match matrix::client_with_stored_session(&session).await {
|
||||
Ok(client) => {
|
||||
if let Err(error) = client.logout().await {
|
||||
error!("Failed to log out old session: {error}");
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to build client to log out old session: {error}")
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(error) = item.delete().await {
|
||||
error!("Failed to delete old session from Secret Service: {error}");
|
||||
};
|
||||
|
||||
if let Err(error) = fs::remove_dir_all(session.path) {
|
||||
error!("Failed to remove database from old session: {error}");
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
SecretError::OldVersion { item, session } => {
|
||||
if session.version == 0 {
|
||||
warn!("Found old session with sled store, removing…");
|
||||
session.delete(item).await
|
||||
} else if session.version < 2 {
|
||||
session.migrate_to_v2(item).await
|
||||
}
|
||||
|
||||
// Restart.
|
||||
spawn!(clone!(@weak self as obj => async move {
|
||||
|
|
Loading…
Reference in New Issue