blob: 9018770676e8829015836ae3d949cdf3423f22e8 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::{EventLog, StoreError};
use sqlx::{Connection, Row};
/// Execution evidence for Sessions sharing an exact canonical working directory.
/// No workspace registry: membership is projected from durable Session bindings.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkspaceFence(pub Vec<WorkspaceSession>);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkspaceSession {
pub session_id: String,
pub revision: i64,
pub event_sequence: i64,
pub orphaned_shells: bool,
}
impl EventLog {
/// Ownership, not quiescence: another retained Session keeps a managed
/// checkout alive regardless of how many past users shared that directory.
pub async fn workspace_has_other_session(
&self,
cwd: &str,
session: &str,
) -> Result<bool, StoreError> {
self.validate_root()?;
crate::sessions::validate_id(session)?;
let (cwd, session) = (cwd.to_owned(), session.to_owned());
self.connection
.run(move |connection| {
Box::pin(async move {
Ok(sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM session_control
WHERE COALESCE(json_extract(configuration, '$.workspace.path'), json_extract(configuration, '$.workspace.hostCwd'))=? AND id<>?)",
)
.bind(cwd)
.bind(session)
.fetch_one(connection)
.await?)
})
})
.await
}
pub async fn workspace_fence(&self, cwd: &str) -> Result<WorkspaceFence, StoreError> {
self.validate_root()?;
let cwd = cwd.to_owned();
self.connection.run(move |connection| Box::pin(async move {
let mut tx = connection.begin().await?;
let rows = sqlx::query(
"SELECT s.id, s.revision,
COALESCE((SELECT MAX(e.sequence) FROM runtime_events e WHERE
(e.event_json IS NOT NULL AND e.event_session=s.id)), 0) AS sequence,
EXISTS(SELECT 1 FROM shell_runs r WHERE r.session_id = s.id AND
json_extract(r.record_json, '$.state.outcome.kind') = 'orphaned') AS orphaned
FROM session_control s WHERE COALESCE(json_extract(s.configuration, '$.workspace.path'), json_extract(s.configuration, '$.workspace.hostCwd')) = ?
ORDER BY s.id"
).bind(cwd).fetch_all(&mut *tx).await?;
let mut result = Vec::with_capacity(rows.len());
for row in rows {
result.push(WorkspaceSession {
session_id: row.try_get("id")?, revision: row.try_get("revision")?,
event_sequence: row.try_get("sequence")?, orphaned_shells: row.try_get("orphaned")?,
});
}
tx.commit().await?;
Ok(WorkspaceFence(result))
})).await
}
}