#![allow(dead_code)]
pub mod harness;
use std::{
cell::{Cell, RefCell},
collections::{HashMap, VecDeque},
rc::Rc,
time::Duration,
};
use gpui_cn::{
CreateFolderError, FileEntry, FilePage, FileSource, FolderEntry, FolderPage, FolderSource,
ListError, PageToken, PathStyle, SourcePath,
};
use gpui_kit::{App, Task};
pub type Answer = Result<(Vec<Item>, Option<PageToken>), ListError>;
#[derive(Clone, Debug)]
pub struct Item {
pub name: String,
pub folder: bool,
}
pub fn folder(name: &str) -> Item {
Item {
name: name.into(),
folder: true,
}
}
pub fn file(name: &str) -> Item {
Item {
name: name.into(),
folder: false,
}
}
#[derive(Default)]
pub struct Shared {
pub dirs: RefCell<HashMap<SourcePath, Vec<Item>>>,
pub log: RefCell<Vec<String>>,
pub failures: RefCell<VecDeque<ListError>>,
pub needs_auth: Cell<bool>,
pub signed_in: Cell<bool>,
pub latency: Cell<Duration>,
pub page_size: Cell<Option<usize>>,
pub cancelled: Rc<Cell<usize>>,
pub answered: Cell<usize>,
pub creatable: Cell<bool>,
pub created: RefCell<Vec<String>>,
pub create_failures: RefCell<VecDeque<CreateFolderError>>,
}
#[derive(Clone)]
pub struct FakeRemote {
pub style: PathStyle,
pub home: Option<SourcePath>,
pub shared: Rc<Shared>,
}
impl FakeRemote {
pub fn new(style: PathStyle) -> Self {
let shared = Shared::default();
shared.creatable.set(true);
Self {
style,
home: None,
shared: Rc::new(shared),
}
}
pub fn posix() -> Self {
Self::new(PathStyle::posix())
.with_home("/home/me")
.with_dir("/", [folder("home"), folder("srv"), file("motd")])
.with_dir("/home", [folder("me"), folder("you")])
.with_dir(
"/home/me",
[
folder("code"),
folder("docs"),
folder(".config"),
file("notes.txt"),
file("todo.md"),
],
)
.with_dir("/home/me/code", [folder("app"), file("main.rs")])
.with_dir("/home/me/docs", [file("a.txt")])
.with_dir("/home/you", [])
.with_dir("/srv", [folder("www")])
}
pub fn windows() -> Self {
Self::new(PathStyle::windows())
.with_home("C:\\Users\\me")
.with_dir(
"C:\\",
[folder("Users"), folder("Windows"), file("pagefile.sys")],
)
.with_dir("C:\\Users", [folder("me"), folder("Public")])
.with_dir(
"C:\\Users\\me",
[folder("Documents"), folder("Code"), file("notes.txt")],
)
.with_dir("C:\\Users\\me\\Code", [file("main.rs")])
.with_dir("D:\\", [folder("data")])
}
pub fn with_home(mut self, home: &str) -> Self {
self.home = Some(self.style.path(home));
self
}
pub fn with_dir(self, dir: &str, items: impl IntoIterator<Item = Item>) -> Self {
let mut items: Vec<Item> = items.into_iter().collect();
let style = self.style;
items.sort_by(|a, b| {
b.folder
.cmp(&a.folder)
.then_with(|| style.compare_names(&a.name, &b.name))
});
self.shared
.dirs
.borrow_mut()
.insert(self.style.path(dir), items);
self
}
pub fn with_latency(self, latency: Duration) -> Self {
self.shared.latency.set(latency);
self
}
pub fn with_page_size(self, size: usize) -> Self {
self.shared.page_size.set(Some(size));
self
}
pub fn requiring_sign_in(self) -> Self {
self.shared.needs_auth.set(true);
self
}
pub fn read_only(self) -> Self {
self.shared.creatable.set(false);
self
}
pub fn fail_create(&self, error: CreateFolderError) {
self.shared.create_failures.borrow_mut().push_back(error);
}
pub fn created(&self) -> Vec<String> {
self.shared.created.borrow().clone()
}
pub fn fail_next(&self, error: ListError) {
self.shared.failures.borrow_mut().push_back(error);
}
pub fn sign_in(&self) {
self.shared.signed_in.set(true);
}
pub fn requested(&self) -> Vec<String> {
self.shared.log.borrow().clone()
}
pub fn cancelled(&self) -> usize {
self.shared.cancelled.get()
}
pub fn names(&self, dir: &str) -> Vec<String> {
self.shared
.dirs
.borrow()
.get(&self.style.path(dir))
.map(|items| items.iter().map(|item| item.name.clone()).collect())
.unwrap_or_default()
}
fn request(&self, dir: &SourcePath, page: Option<PageToken>, cx: &mut App) -> Task<Answer> {
let shared = self.shared.clone();
let label = match &page {
Some(token) => format!("{dir}@{}", token.as_str()),
None => dir.to_string(),
};
shared.log.borrow_mut().push(label);
let dir = dir.clone();
let guard = Guard {
cancelled: shared.cancelled.clone(),
done: false,
};
cx.spawn(async move |cx| {
let mut guard = guard;
let latency = shared.latency.get();
if !latency.is_zero() {
cx.background_executor().timer(latency).await;
}
guard.done = true;
shared.answered.set(shared.answered.get() + 1);
if let Some(error) = shared.failures.borrow_mut().pop_front() {
return Err(error);
}
if shared.needs_auth.get() && !shared.signed_in.get() {
return Err(ListError::AuthRequired(
"Sign in to browse this server".into(),
));
}
let items = shared
.dirs
.borrow()
.get(&dir)
.cloned()
.ok_or(ListError::NotFound)?;
let Some(size) = shared.page_size.get() else {
return Ok((items, None));
};
let start = page.and_then(|t| t.as_str().parse().ok()).unwrap_or(0);
let end = (start + size).min(items.len());
let next = (end < items.len()).then(|| PageToken::new(end.to_string()));
Ok((items[start..end].to_vec(), next))
})
}
}
impl FakeRemote {
fn make(
&self,
parent: &SourcePath,
name: &str,
cx: &mut App,
) -> Task<Result<SourcePath, CreateFolderError>> {
let shared = self.shared.clone();
let style = self.style;
let parent = parent.clone();
let name = name.to_string();
shared.created.borrow_mut().push(format!("{parent}|{name}"));
cx.spawn(async move |cx| {
let latency = shared.latency.get();
if !latency.is_zero() {
cx.background_executor().timer(latency).await;
}
if let Some(error) = shared.create_failures.borrow_mut().pop_front() {
return Err(error);
}
let mut dirs = shared.dirs.borrow_mut();
let items = dirs
.get_mut(&parent)
.ok_or_else(|| CreateFolderError::Other("No such directory".into()))?;
if items
.iter()
.any(|item| style.fold(&item.name) == style.fold(&name))
{
return Err(CreateFolderError::Exists);
}
items.push(folder(&name));
items.sort_by(|a, b| {
b.folder
.cmp(&a.folder)
.then_with(|| style.compare_names(&a.name, &b.name))
});
let path = parent.join(&name);
dirs.insert(path.clone(), Vec::new());
Ok(path)
})
}
}
struct Guard {
cancelled: Rc<Cell<usize>>,
done: bool,
}
impl Drop for Guard {
fn drop(&mut self) {
if !self.done {
self.cancelled.set(self.cancelled.get() + 1);
}
}
}
impl FolderSource for FakeRemote {
fn path_style(&self) -> PathStyle {
self.style
}
fn can_create_folders(&self) -> bool {
self.shared.creatable.get()
}
fn create_folder(
&self,
parent: &SourcePath,
name: &str,
cx: &mut App,
) -> Task<Result<SourcePath, CreateFolderError>> {
self.make(parent, name, cx)
}
fn home(&self) -> Option<SourcePath> {
self.home.clone()
}
fn list(
&self,
dir: &SourcePath,
page: Option<PageToken>,
cx: &mut App,
) -> Task<Result<FolderPage, ListError>> {
let style = self.style;
let task = self.request(dir, page, cx);
cx.spawn(async move |_| {
let (items, next) = task.await?;
let page = FolderPage::new(items.into_iter().filter(|item| item.folder).map(|item| {
let hidden = style.is_hidden_name(&item.name);
FolderEntry::new(item.name).with_hidden(hidden)
}));
Ok(match next {
Some(token) => page.with_next(token),
None => page,
})
})
}
}
impl FileSource for FakeRemote {
fn path_style(&self) -> PathStyle {
self.style
}
fn can_create_folders(&self) -> bool {
self.shared.creatable.get()
}
fn create_folder(
&self,
parent: &SourcePath,
name: &str,
cx: &mut App,
) -> Task<Result<SourcePath, CreateFolderError>> {
self.make(parent, name, cx)
}
fn home(&self) -> Option<SourcePath> {
self.home.clone()
}
fn list(
&self,
dir: &SourcePath,
page: Option<PageToken>,
cx: &mut App,
) -> Task<Result<FilePage, ListError>> {
let style = self.style;
let task = self.request(dir, page, cx);
cx.spawn(async move |_| {
let (items, next) = task.await?;
let page = FilePage::new(items.into_iter().map(|item| {
let hidden = style.is_hidden_name(&item.name);
if item.folder {
FileEntry::folder(item.name).with_hidden(hidden)
} else {
FileEntry::file(item.name).with_hidden(hidden)
}
}));
Ok(match next {
Some(token) => page.with_next(token),
None => page,
})
})
}
}