Test Gitea Runner functionality
This commit is contained in:
26
markbase-webdav/src/webdav/handler.rs
Normal file
26
markbase-webdav/src/webdav/handler.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::path::PathBuf;
|
||||
use dav_server::{DavHandler, localfs::LocalFs, fakels::FakeLs};
|
||||
|
||||
pub struct MarkBaseWebDAV {
|
||||
user_id: String,
|
||||
db_path: PathBuf,
|
||||
}
|
||||
|
||||
impl MarkBaseWebDAV {
|
||||
pub fn new(user_id: String, db_path: PathBuf) -> Self {
|
||||
MarkBaseWebDAV { user_id, db_path }
|
||||
}
|
||||
|
||||
pub fn create_handler(&self) -> DavHandler {
|
||||
let webdav_dir = format!("data/webdav/{}/", self.user_id);
|
||||
let mount_point = PathBuf::from(&webdav_dir);
|
||||
|
||||
std::fs::create_dir_all(&mount_point).expect("Failed to create WebDAV directory");
|
||||
|
||||
DavHandler::builder()
|
||||
.filesystem(LocalFs::new(&mount_point, false, false, false))
|
||||
.locksystem(FakeLs::new())
|
||||
.strip_prefix("/webdav")
|
||||
.build_handler()
|
||||
}
|
||||
}
|
||||
594
markbase-webdav/src/webdav/lock_manager.rs
Normal file
594
markbase-webdav/src/webdav/lock_manager.rs
Normal file
@@ -0,0 +1,594 @@
|
||||
use dav_server::davpath::DavPath;
|
||||
use dav_server::ls::{DavLock, DavLockSystem, LsFuture};
|
||||
use rusqlite::{Connection, params};
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use xmltree::Element;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LockManager {
|
||||
db_path: PathBuf,
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl LockManager {
|
||||
pub fn new(user_id: String, db_path: PathBuf) -> Self {
|
||||
LockManager { db_path, user_id }
|
||||
}
|
||||
|
||||
pub fn init_db(&self) -> Result<(), rusqlite::Error> {
|
||||
let conn = Connection::open(&self.db_path)?;
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS file_locks (
|
||||
lock_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
token TEXT UNIQUE NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
principal TEXT,
|
||||
owner_xml TEXT,
|
||||
timeout_at INTEGER,
|
||||
timeout_secs INTEGER,
|
||||
shared INTEGER NOT NULL DEFAULT 0,
|
||||
deep INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
refreshed_at INTEGER
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_locks_path ON file_locks(path);
|
||||
CREATE INDEX IF NOT EXISTS idx_locks_token ON file_locks(token);
|
||||
CREATE INDEX IF NOT EXISTS idx_locks_user ON file_locks(user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lock_history (
|
||||
history_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
token TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_history_token ON lock_history(token);",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_conn(&self) -> Result<Connection, rusqlite::Error> {
|
||||
Connection::open(&self.db_path)
|
||||
}
|
||||
|
||||
fn lock_to_dav_lock(&self, row: &rusqlite::Row) -> Result<DavLock, rusqlite::Error> {
|
||||
let path_str: String = row.get(2)?;
|
||||
let principal: Option<String> = row.get(4)?;
|
||||
let owner_xml: Option<String> = row.get(5)?;
|
||||
let timeout_at_ts: Option<i64> = row.get(6)?;
|
||||
let timeout_secs: Option<i64> = row.get(7)?;
|
||||
let shared: i32 = row.get(8)?;
|
||||
let deep: i32 = row.get(9)?;
|
||||
|
||||
let timeout_at = timeout_at_ts.map(|ts| {
|
||||
SystemTime::UNIX_EPOCH + Duration::from_secs(ts as u64)
|
||||
});
|
||||
|
||||
let timeout = timeout_secs.map(|s| Duration::from_secs(s as u64));
|
||||
|
||||
let owner = owner_xml.and_then(|xml| {
|
||||
Element::parse(xml.as_bytes()).ok()
|
||||
});
|
||||
|
||||
let token: String = row.get(1)?;
|
||||
|
||||
Ok(DavLock {
|
||||
token,
|
||||
path: Box::new(DavPath::new(&path_str).unwrap_or_else(|_| DavPath::new("/").unwrap())),
|
||||
principal,
|
||||
owner: owner.map(Box::new),
|
||||
timeout_at,
|
||||
timeout,
|
||||
shared: shared != 0,
|
||||
deep: deep != 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn lock_to_dav_lock_from_select(&self, row: &rusqlite::Row) -> Result<DavLock, rusqlite::Error> {
|
||||
let token: String = row.get(0)?;
|
||||
let path_str: String = row.get(1)?;
|
||||
let principal: Option<String> = row.get(2)?;
|
||||
let owner_xml: Option<String> = row.get(3)?;
|
||||
let timeout_at_ts: Option<i64> = row.get(4)?;
|
||||
let timeout_secs: Option<i64> = row.get(5)?;
|
||||
let shared: i32 = row.get(6)?;
|
||||
let deep: i32 = row.get(7)?;
|
||||
|
||||
let timeout_at = timeout_at_ts.map(|ts| {
|
||||
SystemTime::UNIX_EPOCH + Duration::from_secs(ts as u64)
|
||||
});
|
||||
|
||||
let timeout = timeout_secs.map(|s| Duration::from_secs(s as u64));
|
||||
|
||||
let owner = owner_xml.and_then(|xml| {
|
||||
Element::parse(xml.as_bytes()).ok()
|
||||
});
|
||||
|
||||
Ok(DavLock {
|
||||
token,
|
||||
path: Box::new(DavPath::new(&path_str).unwrap_or_else(|_| DavPath::new("/").unwrap())),
|
||||
principal,
|
||||
owner: owner.map(Box::new),
|
||||
timeout_at,
|
||||
timeout,
|
||||
shared: shared != 0,
|
||||
deep: deep != 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn cleanup_expired_locks(&self, conn: &Connection) -> Result<(), rusqlite::Error> {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM file_locks WHERE timeout_at IS NOT NULL AND timeout_at < ?1",
|
||||
params![now],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DavLockSystem for LockManager {
|
||||
fn lock(
|
||||
&'_ self,
|
||||
path: &DavPath,
|
||||
principal: Option<&str>,
|
||||
owner: Option<&Element>,
|
||||
timeout: Option<Duration>,
|
||||
shared: bool,
|
||||
deep: bool,
|
||||
) -> LsFuture<'_, Result<DavLock, DavLock>> {
|
||||
let path_str = path.to_string();
|
||||
let path_owned = path.clone();
|
||||
let token = format!("urn:uuid:{}", Uuid::new_v4());
|
||||
let principal_str = principal.map(|s| s.to_string());
|
||||
let owner_clone = owner.map(|e| e.clone());
|
||||
let owner_xml = owner.and_then(|e| {
|
||||
let mut buf = Vec::new();
|
||||
e.write(&mut buf).ok()?;
|
||||
String::from_utf8(buf).ok()
|
||||
});
|
||||
|
||||
let timeout_secs = timeout.map(|d| d.as_secs() as i64);
|
||||
let timeout_at = timeout.map(|d| {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
now + d.as_secs() as i64
|
||||
});
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
|
||||
Box::pin(async move {
|
||||
let conn = match self.get_conn() {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
return Err(DavLock {
|
||||
token: String::new(),
|
||||
path: Box::new(path_owned.clone()),
|
||||
principal: principal_str.clone(),
|
||||
owner: owner_clone.map(|e| Box::new(e)),
|
||||
timeout_at: None,
|
||||
timeout,
|
||||
shared,
|
||||
deep,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.cleanup_expired_locks(&conn).ok();
|
||||
|
||||
let existing_lock = conn.query_row(
|
||||
"SELECT token, path, principal, owner_xml, timeout_at, timeout_secs, shared, deep
|
||||
FROM file_locks
|
||||
WHERE path = ?1 AND user_id = ?2",
|
||||
params![path_str, &self.user_id],
|
||||
|row| self.lock_to_dav_lock_from_select(row),
|
||||
);
|
||||
|
||||
if let Ok(conflict) = existing_lock {
|
||||
if !(shared && conflict.shared) {
|
||||
return Err(conflict);
|
||||
}
|
||||
}
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO file_locks
|
||||
(token, path, user_id, principal, owner_xml, timeout_at, timeout_secs, shared, deep, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
||||
params![
|
||||
&token,
|
||||
&path_str,
|
||||
&self.user_id,
|
||||
&principal_str,
|
||||
&owner_xml,
|
||||
timeout_at,
|
||||
timeout_secs,
|
||||
if shared { 1 } else { 0 },
|
||||
if deep { 1 } else { 0 },
|
||||
now,
|
||||
],
|
||||
).ok();
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO lock_history (token, path, user_id, action, timestamp)
|
||||
VALUES (?1, ?2, ?3, 'lock', ?4)",
|
||||
params![&token, &path_str, &self.user_id, now],
|
||||
).ok();
|
||||
|
||||
Ok(DavLock {
|
||||
token,
|
||||
path: Box::new(path_owned.clone()),
|
||||
principal: principal_str,
|
||||
owner: owner_clone.map(|e| Box::new(e)),
|
||||
timeout_at: timeout_at.map(|t| SystemTime::UNIX_EPOCH + Duration::from_secs(t as u64)),
|
||||
timeout,
|
||||
shared,
|
||||
deep,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn unlock(&'_ self, path: &DavPath, token: &str) -> LsFuture<'_, Result<(), ()>> {
|
||||
let path_str = path.to_string();
|
||||
let token_str = token.to_string();
|
||||
|
||||
Box::pin(async move {
|
||||
let conn = match self.get_conn() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
|
||||
let rows = conn.execute(
|
||||
"DELETE FROM file_locks WHERE token = ?1 AND path = ?2 AND user_id = ?3",
|
||||
params![&token_str, &path_str, &self.user_id],
|
||||
);
|
||||
|
||||
if let Ok(deleted) = rows {
|
||||
if deleted > 0 {
|
||||
conn.execute(
|
||||
"INSERT INTO lock_history (token, path, user_id, action, timestamp)
|
||||
VALUES (?1, ?2, ?3, 'unlock', ?4)",
|
||||
params![&token_str, &path_str, &self.user_id, now],
|
||||
).ok();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(())
|
||||
})
|
||||
}
|
||||
|
||||
fn refresh(
|
||||
&'_ self,
|
||||
path: &DavPath,
|
||||
token: &str,
|
||||
timeout: Option<Duration>,
|
||||
) -> LsFuture<'_, Result<DavLock, ()>> {
|
||||
let path_str = path.to_string();
|
||||
let token_str = token.to_string();
|
||||
let timeout_secs = timeout.map(|d| d.as_secs() as i64);
|
||||
let timeout_at = timeout.map(|d| {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
now + d.as_secs() as i64
|
||||
});
|
||||
|
||||
Box::pin(async move {
|
||||
let conn = match self.get_conn() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
|
||||
let updated = conn.execute(
|
||||
"UPDATE file_locks
|
||||
SET timeout_at = ?1, timeout_secs = ?2, refreshed_at = ?3
|
||||
WHERE token = ?4 AND path = ?5 AND user_id = ?6",
|
||||
params![timeout_at, timeout_secs, now, &token_str, &path_str, &self.user_id],
|
||||
);
|
||||
|
||||
if let Ok(rows) = updated {
|
||||
if rows > 0 {
|
||||
conn.execute(
|
||||
"INSERT INTO lock_history (token, path, user_id, action, timestamp)
|
||||
VALUES (?1, ?2, ?3, 'refresh', ?4)",
|
||||
params![&token_str, &path_str, &self.user_id, now],
|
||||
).ok();
|
||||
|
||||
return conn.query_row(
|
||||
"SELECT * FROM file_locks WHERE token = ?1",
|
||||
params![&token_str],
|
||||
|row| self.lock_to_dav_lock(row),
|
||||
).map(|lock| {
|
||||
if let Some(t) = timeout {
|
||||
DavLock {
|
||||
timeout: Some(t),
|
||||
..lock
|
||||
}
|
||||
} else {
|
||||
lock
|
||||
}
|
||||
}).map_err(|_| ());
|
||||
}
|
||||
}
|
||||
|
||||
Err(())
|
||||
})
|
||||
}
|
||||
|
||||
fn check(
|
||||
&'_ self,
|
||||
path: &DavPath,
|
||||
principal: Option<&str>,
|
||||
ignore_principal: bool,
|
||||
deep: bool,
|
||||
submitted_tokens: &[String],
|
||||
) -> LsFuture<'_, Result<(), DavLock>> {
|
||||
let path_str = path.to_string();
|
||||
let path_owned = path.clone();
|
||||
let principal_str = principal.map(|s| s.to_string());
|
||||
let tokens = submitted_tokens.to_vec();
|
||||
let user_id = self.user_id.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let conn = match self.get_conn() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
self.cleanup_expired_locks(&conn).ok();
|
||||
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT * FROM file_locks WHERE path = ?1 AND user_id = ?2"
|
||||
).map_err(|_| DavLock {
|
||||
token: String::new(),
|
||||
path: Box::new(path_owned.clone()),
|
||||
principal: None,
|
||||
owner: None,
|
||||
timeout_at: None,
|
||||
timeout: None,
|
||||
shared: false,
|
||||
deep: false,
|
||||
})?;
|
||||
|
||||
let locks = stmt.query_map(params![&path_str, &user_id], |row| {
|
||||
self.lock_to_dav_lock(row)
|
||||
}).map_err(|_| DavLock {
|
||||
token: String::new(),
|
||||
path: Box::new(path_owned.clone()),
|
||||
principal: None,
|
||||
owner: None,
|
||||
timeout_at: None,
|
||||
timeout: None,
|
||||
shared: false,
|
||||
deep: false,
|
||||
})?;
|
||||
|
||||
for lock_result in locks {
|
||||
if let Ok(lock) = lock_result {
|
||||
if tokens.contains(&lock.token) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ignore_principal {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ref lock_principal) = lock.principal {
|
||||
if let Some(ref check_principal) = principal_str {
|
||||
if lock_principal == check_principal {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if deep && lock.deep {
|
||||
return Err(lock);
|
||||
}
|
||||
|
||||
if !deep {
|
||||
return Err(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn discover(&'_ self, path: &DavPath) -> LsFuture<'_, Vec<DavLock>> {
|
||||
let path_str = path.to_string();
|
||||
let user_id = self.user_id.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let conn = match self.get_conn() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
|
||||
self.cleanup_expired_locks(&conn).ok();
|
||||
|
||||
let mut stmt = match conn.prepare(
|
||||
"SELECT * FROM file_locks WHERE path = ?1 AND user_id = ?2"
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
|
||||
let locks = stmt.query_map(params![&path_str, &user_id], |row| {
|
||||
self.lock_to_dav_lock(row)
|
||||
});
|
||||
|
||||
match locks {
|
||||
Ok(l) => l.filter_map(|r| r.ok()).collect(),
|
||||
Err(_) => Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn delete(&'_ self, path: &DavPath) -> LsFuture<'_, Result<(), ()>> {
|
||||
let path_str = path.to_string();
|
||||
let user_id = self.user_id.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let conn = match self.get_conn() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO lock_history (token, path, user_id, action, timestamp)
|
||||
SELECT token, path, user_id, 'delete', ?1
|
||||
FROM file_locks
|
||||
WHERE path LIKE ?2 AND user_id = ?3",
|
||||
params![now, format!("{}%", path_str), &user_id],
|
||||
).ok();
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM file_locks WHERE path LIKE ?1 AND user_id = ?2",
|
||||
params![format!("{}%", path_str), &user_id],
|
||||
).map(|_| ()).map_err(|_| ())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LockManager {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "LockManager(user={}, db={:?})", self.user_id, self.db_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use dav_server::davpath::DavPath;
|
||||
use std::time::Duration;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_lock_manager_creation() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let db_path = temp_dir.path().join("test_locks.sqlite");
|
||||
let manager = LockManager::new("test_user".to_string(), db_path.clone());
|
||||
|
||||
assert_eq!(manager.user_id, "test_user");
|
||||
assert_eq!(manager.db_path, db_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_init_db() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let db_path = temp_dir.path().join("test_locks.sqlite");
|
||||
let manager = LockManager::new("test_user".to_string(), db_path);
|
||||
|
||||
manager.init_db().expect("Failed to initialize database");
|
||||
|
||||
let conn = Connection::open(&manager.db_path).unwrap();
|
||||
conn.execute("SELECT * FROM file_locks LIMIT 1", []).unwrap();
|
||||
conn.execute("SELECT * FROM lock_history LIMIT 1", []).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_lock_and_unlock() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let db_path = temp_dir.path().join("test_locks.sqlite");
|
||||
let manager = LockManager::new("test_user".to_string(), db_path);
|
||||
manager.init_db().unwrap();
|
||||
|
||||
let path = DavPath::new("/test/file.txt").unwrap();
|
||||
|
||||
let lock_result = manager.lock(&path, None, None, None, false, false).await;
|
||||
|
||||
match lock_result {
|
||||
Ok(lock) => {
|
||||
assert!(lock.token.starts_with("urn:uuid:"));
|
||||
assert_eq!(lock.path.as_ref(), &path);
|
||||
|
||||
let unlock_result = manager.unlock(&path, &lock.token).await;
|
||||
assert!(unlock_result.is_ok());
|
||||
}
|
||||
Err(_) => {
|
||||
panic!("Lock should succeed on first attempt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_lock_conflict() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let db_path = temp_dir.path().join("test_locks.sqlite");
|
||||
let manager = LockManager::new("test_user".to_string(), db_path);
|
||||
manager.init_db().unwrap();
|
||||
|
||||
let path = DavPath::new("/test/file.txt").unwrap();
|
||||
|
||||
let lock1 = manager.lock(&path, Some("user1"), None, None, false, false).await;
|
||||
assert!(lock1.is_ok());
|
||||
|
||||
let lock2 = manager.lock(&path, Some("user2"), None, None, false, false).await;
|
||||
assert!(lock2.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_lock_discover() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let db_path = temp_dir.path().join("test_locks.sqlite");
|
||||
let manager = LockManager::new("test_user".to_string(), db_path);
|
||||
manager.init_db().unwrap();
|
||||
|
||||
let path = DavPath::new("/test/file.txt").unwrap();
|
||||
|
||||
let lock = manager.lock(&path, None, None, None, false, false).await.unwrap();
|
||||
|
||||
let discovered = manager.discover(&path).await;
|
||||
assert_eq!(discovered.len(), 1);
|
||||
assert_eq!(discovered[0].token, lock.token);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_lock_refresh() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let db_path = temp_dir.path().join("test_locks.sqlite");
|
||||
let manager = LockManager::new("test_user".to_string(), db_path);
|
||||
manager.init_db().unwrap();
|
||||
|
||||
let path = DavPath::new("/test/file.txt").unwrap();
|
||||
let timeout = Duration::from_secs(60);
|
||||
|
||||
let lock = manager.lock(&path, None, None, Some(timeout), false, false).await.unwrap();
|
||||
|
||||
let refreshed = manager.refresh(&path, &lock.token, Some(Duration::from_secs(120))).await;
|
||||
assert!(refreshed.is_ok());
|
||||
|
||||
let refreshed_lock = refreshed.unwrap();
|
||||
assert_eq!(refreshed_lock.timeout, Some(Duration::from_secs(120)));
|
||||
}
|
||||
}
|
||||
4
markbase-webdav/src/webdav/mod.rs
Normal file
4
markbase-webdav/src/webdav/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod handler;
|
||||
pub mod lock_manager;
|
||||
|
||||
pub use handler::MarkBaseWebDAV;
|
||||
Reference in New Issue
Block a user