Add delete route that allow recursive delete for directories

This commit is contained in:
Zoe Roux 2024-04-29 01:43:35 +02:00
parent fccc5b6ad9
commit 9e089b21ed
No known key found for this signature in database
3 changed files with 51 additions and 21 deletions

View File

@ -84,6 +84,27 @@ public class MiscRepository(
.ToListAsync(); .ToListAsync();
} }
public async Task<int> DeletePath(string path, bool recurse)
{
// Make sure to include a path separator to prevents deletions from things like:
// DeletePath("/video/abc", true) -> /video/abdc (should not be deleted)
string dirPath = path.EndsWith("/") ? path : $"{path}/";
int count = await context
.Episodes.Where(x => x.Path == path || (recurse && x.Path.StartsWith(dirPath)))
.ExecuteDeleteAsync();
count += await context
.Movies.Where(x => x.Path == path || (recurse && x.Path.StartsWith(dirPath)))
.ExecuteDeleteAsync();
await context
.Issues.Where(x =>
x.Domain == "scanner"
&& (x.Cause == path || (recurse && x.Cause.StartsWith(dirPath)))
)
.ExecuteDeleteAsync();
return count;
}
public async Task<ICollection<RefreshableItem>> GetRefreshableItems(DateTime end) public async Task<ICollection<RefreshableItem>> GetRefreshableItems(DateTime end)
{ {
IQueryable<RefreshableItem> GetItems<T>() IQueryable<RefreshableItem> GetItems<T>()

View File

@ -30,7 +30,7 @@ namespace Kyoo.Core.Api;
/// Private APIs only used for other services. Can change at any time without notice. /// Private APIs only used for other services. Can change at any time without notice.
/// </summary> /// </summary>
[ApiController] [ApiController]
[Permission(nameof(Misc), Kind.Read, Group = Group.Admin)] [PartialPermission(nameof(Misc), Group = Group.Admin)]
public class Misc(MiscRepository repo) : BaseApi public class Misc(MiscRepository repo) : BaseApi
{ {
/// <summary> /// <summary>
@ -38,18 +38,40 @@ public class Misc(MiscRepository repo) : BaseApi
/// </summary> /// </summary>
/// <returns>The list of paths known to Kyoo.</returns> /// <returns>The list of paths known to Kyoo.</returns>
[HttpGet("/paths")] [HttpGet("/paths")]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<string>> GetAllPaths() public Task<ICollection<string>> GetAllPaths()
{ {
return repo.GetRegisteredPaths(); return repo.GetRegisteredPaths();
} }
/// <summary>
/// Delete item at path.
/// </summary>
/// <param name="path">The path to delete.</param>
/// <param name="recursive">
/// If true, the path will be considered as a directory and every children will be removed.
/// </param>
/// <returns>Nothing</returns>
[HttpDelete("/paths")]
[PartialPermission(Kind.Delete)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> DeletePath(
[FromQuery] string path,
[FromQuery] bool recursive = false
)
{
await repo.DeletePath(path, recursive);
return NoContent();
}
/// <summary> /// <summary>
/// List items to refresh. /// List items to refresh.
/// </summary> /// </summary>
/// <param name="date">The upper limit for the refresh date.</param> /// <param name="date">The upper limit for the refresh date.</param>
/// <returns>The items that should be refreshed before the given date</returns> /// <returns>The items that should be refreshed before the given date</returns>
[HttpGet("/refreshables")] [HttpGet("/refreshables")]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<RefreshableItem>> GetAllPaths([FromQuery] DateTime? date) public Task<ICollection<RefreshableItem>> GetAllPaths([FromQuery] DateTime? date)
{ {

View File

@ -104,29 +104,16 @@ class KyooClient:
async def delete( async def delete(
self, self,
path: str, path: str,
type: Literal["episode", "movie"] | None = None,
): ):
logger.info("Deleting %s", path) logger.info("Deleting %s", path)
if type is None or type == "movie": async with self.client.delete(
async with self.client.delete( f'{self._url}/paths?recursive=true&path={quote(path)}',
f'{self._url}/movies?filter=path eq "{quote(path)}"', headers={"X-API-Key": self._api_key},
headers={"X-API-Key": self._api_key}, ) as r:
) as r: if not r.ok:
if not r.ok: logger.error(f"Request error: {await r.text()}")
logger.error(f"Request error: {await r.text()}") r.raise_for_status()
r.raise_for_status()
if type is None or type == "episode":
async with self.client.delete(
f'{self._url}/episodes?filter=path eq "{quote(path)}"',
headers={"X-API-Key": self._api_key},
) as r:
if not r.ok:
logger.error(f"Request error: {await r.text()}")
r.raise_for_status()
await self.delete_issue(path)
async def get(self, path: str): async def get(self, path: str):
async with self.client.get( async with self.client.get(