Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions app/user_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ async def listuserdata(request):
- recurse (optional): If "true", recursively list files in subdirectories.
- full_info (optional): If "true", return detailed file information (path, size, modified time).
- split (optional): If "true", split file paths into components (only applies when full_info is false).
- emptyDirs (optional): If "true", include empty directories in the listing.

Returns:
- 400: If 'dir' parameter is missing.
Expand All @@ -172,6 +173,7 @@ async def listuserdata(request):
recurse = request.rel_url.query.get('recurse', '').lower() == "true"
full_info = request.rel_url.query.get('full_info', '').lower() == "true"
split_path = request.rel_url.query.get('split', '').lower() == "true"
include_empty_dirs = request.rel_url.query.get('emptyDirs', '').lower() == "true"

# Use different patterns based on whether we're recursing or not
if recurse:
Expand All @@ -185,15 +187,29 @@ def process_full_path(full_path: str) -> FileInfo | str | list[str]:

rel_path = os.path.relpath(full_path, path).replace(os.sep, '/')
if split_path:
return [rel_path] + rel_path.split('/')
if os.path.isdir(full_path):
dirname, basename = rel_path, ""
else:
head, tail = rel_path.rsplit('/', 1) if '/' in rel_path else ("", rel_path)
dirname, basename = head, tail
return [rel_path, dirname, basename]

return rel_path

results = [
process_full_path(full_path)
for full_path in glob.glob(pattern, recursive=recurse)
if os.path.isfile(full_path)
]
enum_entries = glob.glob(pattern, recursive=recurse)
results = []
for full_path in enum_entries:
is_dir = os.path.isdir(full_path)

if is_dir:
# skip every dir unless we're explicitly including empty ones
if not include_empty_dirs:
continue
# when including dirs, only keep the empty ones
if os.listdir(full_path):
continue

results.append(process_full_path(full_path))

return web.json_response(results)

Expand Down
118 changes: 118 additions & 0 deletions tests-unit/prompt_server_test/user_manager_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,124 @@ async def test_listuserdata_normalized_separator(aiohttp_client, app, tmp_path):
assert "\\" not in result[0]["path"] # Ensure backslash is not present
assert result[0]["path"] == "subdir/file1.txt"

async def test_listuserdata_include_empty_dirs(aiohttp_client, app, tmp_path):
# Arrange
test_dir = tmp_path / "test_dir"
empty_subdir = test_dir / "empty_subdir"
file1 = test_dir / "file1.txt"

os.makedirs(test_dir)
os.makedirs(empty_subdir)
with open(file1, "w") as f:
f.write("test")

client = await aiohttp_client(app)

# Act
resp = await client.get("/userdata?dir=test_dir&emptyDirs=true")

# Assert
assert resp.status == 200
result = await resp.json()
assert set(result) == {"file1.txt", "empty_subdir"}

async def test_listuserdata_exclude_empty_dirs_default(aiohttp_client, app, tmp_path):
# Arrange
test_dir = tmp_path / "test_dir"
empty_subdir = test_dir / "empty_subdir"
file1 = test_dir / "file1.txt"

os.makedirs(test_dir)
os.makedirs(empty_subdir)
with open(file1, "w") as f:
f.write("test")

client = await aiohttp_client(app)

# Act
resp = await client.get("/userdata?dir=test_dir") # emptyDirs defaults to false

# Assert
assert resp.status == 200
result = await resp.json()
assert result == ["file1.txt"]

async def test_listuserdata_recursive_include_empty_dirs(aiohttp_client, app, tmp_path):
# Arrange
base_dir = tmp_path / "test_dir"
occupied = base_dir / "occupied_directory"
empty = base_dir / "empty_directory"
file1 = occupied / "file1.txt"

os.makedirs(occupied)
os.makedirs(empty)
with open(file1, "w") as f:
f.write("content")

client = await aiohttp_client(app)

# Act
resp = await client.get("/userdata?dir=test_dir&recurse=true&emptyDirs=true")

# Assert
assert resp.status == 200
result = await resp.json()
assert set(result) == {"occupied_directory/file1.txt", "empty_directory"}

async def test_listuserdata_full_info_include_empty_dirs(aiohttp_client, app, tmp_path):
# Arrange
test_dir = tmp_path / "test_dir"
file1 = test_dir / "file1.txt"
empty = test_dir / "empty_subdir"
os.makedirs(test_dir)
os.makedirs(empty)
with open(file1, "w") as f:
f.write("content")

client = await aiohttp_client(app)

# Act
resp = await client.get("/userdata?dir=test_dir&full_info=true&emptyDirs=true")

# Assert
assert resp.status == 200
result = await resp.json()
paths = {info["path"] for info in result}
assert paths == {"file1.txt", "empty_subdir"}
for info in result:
assert "size" in info
assert "modified" in info

async def test_listuserdata_recurse_split_include_empty_dirs(aiohttp_client, app, tmp_path):
# Arrange
test_dir = tmp_path / "test_dir"
file1 = test_dir / "file1.txt"
empty = test_dir / "empty_subdir"
occupying_dir = test_dir / "occupied_directory"
another_occupying_dir = occupying_dir / "another_occupied_directory"
file2 = another_occupying_dir / "file2.txt"
os.makedirs(test_dir)
os.makedirs(occupying_dir)
os.makedirs(another_occupying_dir)
os.makedirs(empty)
with open(file1, "w") as f:
f.write("content")
with open(file2, "w") as f:
f.write("nested content")

client = await aiohttp_client(app)

# Act
resp = await client.get("/userdata?dir=test_dir&split=true&emptyDirs=true&recurse=true")

# Assert
assert resp.status == 200
result = await resp.json()
assert set(tuple(r) for r in result) == {
("file1.txt", "", "file1.txt"),
("empty_subdir", "empty_subdir", ""),
("occupied_directory/another_occupied_directory/file2.txt", "occupied_directory/another_occupied_directory", "file2.txt"),
}

async def test_post_userdata_new_file(aiohttp_client, app, tmp_path):
client = await aiohttp_client(app)
Expand Down