use std::collections::{HashMap, HashSet};
use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set};
use crate::db::entities::{
group, setting, workspace, workspace_group, GroupEntity, HiddenWorkspaceEntity,
SettingEntity, WorkspaceEntity, WorkspaceGroupEntity,
};
use crate::error::Result;
pub(crate) async fn load_workspaces_by_names(
conn: &DatabaseConnection,
names: &[String],
) -> Result<HashMap<String, workspace::Model>> {
if names.is_empty() {
return Ok(HashMap::new());
}
let rows = WorkspaceEntity::find()
.filter(workspace::Column::Name.is_in(names.iter().cloned()))
.all(conn)
.await?;
Ok(rows.into_iter().map(|w| (w.name.clone(), w)).collect())
}
pub(crate) async fn load_workspaces_by_ids(
conn: &DatabaseConnection,
ws_ids: &[i32],
) -> Result<HashMap<i32, workspace::Model>> {
if ws_ids.is_empty() {
return Ok(HashMap::new());
}
let rows = WorkspaceEntity::find()
.filter(workspace::Column::Id.is_in(ws_ids.iter().cloned()))
.all(conn)
.await?;
Ok(rows.into_iter().map(|w| (w.id, w)).collect())
}
pub(crate) async fn load_memberships_by_workspace_ids(
conn: &DatabaseConnection,
ws_ids: &[i32],
) -> Result<HashMap<i32, Vec<workspace_group::Model>>> {
if ws_ids.is_empty() {
return Ok(HashMap::new());
}
let rows = WorkspaceGroupEntity::find()
.filter(workspace_group::Column::WorkspaceId.is_in(ws_ids.iter().cloned()))
.all(conn)
.await?;
let mut map: HashMap<i32, Vec<workspace_group::Model>> = HashMap::new();
for m in rows {
map.entry(m.workspace_id).or_default().push(m);
}
Ok(map)
}
pub(crate) async fn load_memberships_by_group_ids(
conn: &DatabaseConnection,
group_ids: &[i32],
) -> Result<HashMap<i32, Vec<workspace_group::Model>>> {
if group_ids.is_empty() {
return Ok(HashMap::new());
}
let rows = WorkspaceGroupEntity::find()
.filter(workspace_group::Column::GroupId.is_in(group_ids.iter().cloned()))
.all(conn)
.await?;
let mut map: HashMap<i32, Vec<workspace_group::Model>> = HashMap::new();
for m in rows {
map.entry(m.group_id).or_default().push(m);
}
Ok(map)
}
pub(crate) async fn load_group_names_by_ids(
conn: &DatabaseConnection,
group_ids: &[i32],
) -> Result<HashMap<i32, String>> {
if group_ids.is_empty() {
return Ok(HashMap::new());
}
let rows = GroupEntity::find()
.filter(group::Column::Id.is_in(group_ids.iter().cloned()))
.all(conn)
.await?;
Ok(rows.into_iter().map(|g| (g.id, g.name)).collect())
}
pub(crate) async fn load_hidden_pairs(
conn: &DatabaseConnection,
) -> Result<HashSet<(i32, i32)>> {
let rows = HiddenWorkspaceEntity::find().all(conn).await?;
Ok(rows.into_iter().map(|r| (r.workspace_id, r.group_id)).collect())
}
pub(crate) async fn get_setting(
conn: &DatabaseConnection,
key: &str,
) -> Result<Option<String>> {
let row = SettingEntity::find_by_id(key.to_string()).one(conn).await?;
Ok(row.map(|r| r.value))
}
pub(crate) async fn set_setting(
conn: &DatabaseConnection,
key: &str,
value: &str,
) -> Result<()> {
let existing = SettingEntity::find_by_id(key.to_string()).one(conn).await?;
match existing {
Some(row) => {
let mut m: setting::ActiveModel = row.into();
m.value = Set(value.to_string());
m.update(conn).await?;
}
None => {
let m = setting::ActiveModel {
key: Set(key.to_string()),
value: Set(value.to_string()),
};
m.insert(conn).await?;
}
}
Ok(())
}
pub(crate) async fn get_bool_setting(
conn: &DatabaseConnection,
key: &str,
default: bool,
) -> Result<bool> {
Ok(match get_setting(conn, key).await? {
Some(v) => v == "true",
None => default,
})
}
pub(crate) fn is_visible(
is_global: bool,
membership_group_names: &[String],
active_group: Option<&str>,
is_hidden_in_active_group: bool,
show_hidden: bool,
) -> bool {
if is_hidden_in_active_group && !show_hidden {
return false;
}
if is_global {
return true;
}
for name in membership_group_names {
if active_group == Some(name.as_str()) {
return true;
}
}
membership_group_names.is_empty() && active_group.is_none()
}
pub(crate) async fn compute_visible_workspaces(
conn: &DatabaseConnection,
sway_names: &[String],
active_group: Option<&str>,
) -> Result<Vec<String>> {
let show_hidden = get_bool_setting(conn, setting::SHOW_HIDDEN_WORKSPACES, false).await?;
let ws_map = load_workspaces_by_names(conn, sway_names).await?;
let ws_ids: Vec<i32> = ws_map.values().map(|w| w.id).collect();
let memberships_map = load_memberships_by_workspace_ids(conn, &ws_ids).await?;
let group_ids: Vec<i32> = memberships_map
.values()
.flat_map(|ms| ms.iter().map(|m| m.group_id))
.collect::<HashSet<_>>()
.into_iter()
.collect();
let group_name_map = load_group_names_by_ids(conn, &group_ids).await?;
let active_group_id: Option<i32> = match active_group {
Some(name) => GroupEntity::find()
.filter(group::Column::Name.eq(name))
.one(conn)
.await?
.map(|g| g.id),
None => None,
};
let hidden_pairs = load_hidden_pairs(conn).await?;
let mut visible = Vec::new();
let mut seen = HashSet::new();
for name in sway_names {
if seen.contains(name) {
continue;
}
if let Some(ws) = ws_map.get(name) {
let memberships = memberships_map.get(&ws.id).map(|v| v.as_slice()).unwrap_or(&[]);
let membership_group_names: Vec<String> = memberships
.iter()
.filter_map(|m| group_name_map.get(&m.group_id).cloned())
.collect();
let is_hidden_here = match active_group_id {
Some(gid) => hidden_pairs.contains(&(ws.id, gid)),
None => false,
};
if is_visible(
ws.is_global,
&membership_group_names,
active_group,
is_hidden_here,
show_hidden,
) {
visible.push(name.clone());
seen.insert(name.clone());
}
}
}
visible.sort();
Ok(visible)
}