Directories can be deleted in /api/files/delete.

This commit is contained in:
Roland 2023-08-15 13:47:21 -06:00
parent 3256c89aff
commit 3aa7b47d61
No known key found for this signature in database
GPG key ID: 9D6DBD3ACF11AB93
4 changed files with 39 additions and 65 deletions

View file

@ -1,5 +1,4 @@
import pathlib
import shutil
from http import HTTPStatus
from multiprocessing import Manager
from typing import List
@ -48,7 +47,8 @@ async def get_file(_: Request, body: schema.FileRequest):
@bp.post('/delete')
@openapi.definition(
summary='Delete a single file. Returns an error if WROL Mode is enabled.',
summary='Delete files or directories. Directories are deleted recursively.'
' Returns an error if WROL Mode is enabled.',
body=schema.DeleteRequest,
)
@validate(schema.DeleteRequest)
@ -56,7 +56,7 @@ async def delete_file(_: Request, body: schema.DeleteRequest):
paths = [i for i in body.paths if i]
if not paths:
raise InvalidFile('paths cannot be empty')
lib.delete(*paths)
await lib.delete(*paths)
return response.empty()
@ -251,28 +251,6 @@ async def post_rename(_: Request, body: schema.Rename):
return response.empty(HTTPStatus.NO_CONTENT)
@bp.post('/delete_directory')
@openapi.definition(
summary='Delete a directory in the media directory.',
body=schema.Directory,
)
@validate(schema.Directory)
@wrol_mode_check
async def delete_directory(_: Request, body: schema.Directory):
path = get_media_directory() / body.path
if path.is_dir():
lib.delete_directory(path, recursive=True)
else:
raise FileNotFoundError(f'No directory found: {path}')
if PYTEST:
await lib.refresh_files([path, ])
else:
background_task(lib.refresh_files([path, ]))
return response.empty()
@bp.post('/tag')
@validate(schema.TagFileGroupPost)
async def post_tag_file_group(_, body: schema.TagFileGroupPost):

View file

@ -134,7 +134,7 @@ def list_directories_contents(directories_: List[str]) -> Dict:
@wrol_mode_check
def delete(*paths: Union[str, pathlib.Path]):
async def delete(*paths: Union[str, pathlib.Path]):
"""
Delete a file or directory in the media directory.
@ -166,10 +166,16 @@ def delete(*paths: Union[str, pathlib.Path]):
raise FileGroupIsTagged(f"Cannot delete {file_group} because it is tagged")
for path in paths:
if path.is_dir():
delete_directory(path)
delete_directory(path, recursive=True)
else:
path.unlink()
coro = refresh_files(paths)
if PYTEST:
await coro
else:
background_task(coro)
FILE_NAME_REGEX = re.compile(r'[_ .]')

View file

@ -4,7 +4,6 @@ from unittest import mock
import pytest
from wrolpi.errors import WROLModeEnabled
from wrolpi.files import lib
from wrolpi.files.models import FileGroup
from wrolpi.tags import TagFile
@ -113,7 +112,7 @@ def test_list_files_api(test_session, test_client, make_files_structure, test_di
def test_delete_file(test_session, test_client, make_files_structure, test_directory):
files = ['bar.txt', 'baz/']
files = ['bar.txt', 'baz/', 'foo']
make_files_structure(files)
# Delete a file.
@ -503,8 +502,8 @@ async def test_directory_crud(test_session, test_async_client, test_directory, a
# Deletion is recursive.
assert (test_directory / 'foo/baz/asdf.txt').is_file()
content = dict(path='foo/baz')
request, response = await test_async_client.post('/api/files/delete_directory', content=json.dumps(content))
content = dict(paths=['foo/baz', ])
request, response = await test_async_client.post('/api/files/delete', content=json.dumps(content))
assert response.status_code == HTTPStatus.NO_CONTENT
assert not (test_directory / 'foo/baz').exists()
assert not (test_directory / 'foo/baz/asdf.txt').exists()
@ -573,27 +572,16 @@ async def test_rename_directory(test_session, test_directory, make_files_structu
@pytest.mark.asyncio
async def test_delete_directory_tagged(test_session, test_directory, make_files_structure, test_async_client,
tag_factory, insert_file_group):
"""Cannot delete a directory if it contained a tagged FileGroup."""
bar, = make_files_structure({
'foo/bar.txt': 'bar'
}, file_groups=True, session=test_session)
bar: FileGroup
tag = tag_factory()
bar.add_tag(tag)
test_session.commit()
async def test_delete_directory_recursive(test_session, test_directory, make_files_structure, test_async_client,
assert_files):
make_files_structure(['dir/foo', 'dir/bar', 'empty'])
await lib.refresh_files()
assert test_session.query(FileGroup).count() == 3
# Cannot deleted tagged file.
content = dict(path=str(bar.primary_path.parent))
request, response = await test_async_client.post('/api/files/delete_directory', content=json.dumps(content))
assert response.status_code == HTTPStatus.CONFLICT
assert (test_directory / 'foo').is_dir()
# Remove tag, directory is deleted recursively.
bar.remove_tag(tag)
test_session.commit()
content = dict(path=str(bar.primary_path.parent))
request, response = await test_async_client.post('/api/files/delete_directory', content=json.dumps(content))
content = {'paths': ['dir/', ]}
request, response = await test_async_client.post('/api/files/delete', content=json.dumps(content))
assert response.status_code == HTTPStatus.NO_CONTENT
assert not (test_directory / 'foo').exists()
assert test_session.query(FileGroup).count() == 1
empty = test_session.query(FileGroup).one()
assert empty.primary_path.name == 'empty'

View file

@ -21,7 +21,8 @@ from wrolpi.tags import TagFile
from wrolpi.vars import PROJECT_DIR
def test_delete(test_session, make_files_structure, test_directory):
@pytest.mark.asyncio
async def test_delete(test_session, make_files_structure, test_directory):
"""
File in the media directory can be deleted.
"""
@ -31,34 +32,35 @@ def test_delete(test_session, make_files_structure, test_directory):
'baz/',
])
lib.delete('bar.txt')
await lib.delete('bar.txt')
assert (test_directory / 'archives/foo.txt').is_file()
assert not (test_directory / 'bar.txt').exists()
assert (test_directory / 'baz').is_dir()
lib.delete('archives/foo.txt')
await lib.delete('archives/foo.txt')
assert not (test_directory / 'archives/foo.txt').exists()
assert not (test_directory / 'bar.txt').exists()
# Can also delete directories.
lib.delete('baz')
await lib.delete('baz')
assert not (test_directory / 'baz').exists()
with pytest.raises(InvalidFile):
lib.delete('does not exist')
await lib.delete('does not exist')
# Cannot delete the media directory.
with pytest.raises(InvalidFile):
lib.delete('.')
await lib.delete('.')
def test_delete_link(test_session, test_directory):
@pytest.mark.asyncio
async def test_delete_link(test_session, test_directory):
"""Links can be deleted."""
foo, bar = test_directory / 'foo', test_directory / 'bar'
foo.touch()
bar.symlink_to(foo)
lib.delete(bar)
await lib.delete(bar)
@pytest.mark.asyncio
@ -74,11 +76,11 @@ async def test_delete_tagged(test_session, make_files_structure, tag_factory, vi
# Neither file can be deleted.
with pytest.raises(FileGroupIsTagged):
lib.delete('foo/bar.txt')
await lib.delete('foo/bar.txt')
with pytest.raises(FileGroupIsTagged):
lib.delete('foo/bar.mp4')
await lib.delete('foo/bar.mp4')
with pytest.raises(FileGroupIsTagged):
lib.delete('foo')
await lib.delete('foo')
@pytest.mark.asyncio
@ -87,7 +89,7 @@ async def test_delete_nested(test_session, make_files_structure):
make_files_structure(['foo/bar'])
with pytest.raises(InvalidFile):
lib.delete('foo', 'foo/bar')
await lib.delete('foo', 'foo/bar')
@pytest.mark.parametrize(