mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Fix folder renaming (#449)
This commit is contained in:
commit
7daa10ef8d
@ -84,6 +84,27 @@ public class MiscRepository(
|
||||
.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)
|
||||
{
|
||||
IQueryable<RefreshableItem> GetItems<T>()
|
||||
|
@ -30,7 +30,7 @@ namespace Kyoo.Core.Api;
|
||||
/// Private APIs only used for other services. Can change at any time without notice.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Permission(nameof(Misc), Kind.Read, Group = Group.Admin)]
|
||||
[PartialPermission(nameof(Misc), Group = Group.Admin)]
|
||||
public class Misc(MiscRepository repo) : BaseApi
|
||||
{
|
||||
/// <summary>
|
||||
@ -38,18 +38,40 @@ public class Misc(MiscRepository repo) : BaseApi
|
||||
/// </summary>
|
||||
/// <returns>The list of paths known to Kyoo.</returns>
|
||||
[HttpGet("/paths")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Task<ICollection<string>> GetAllPaths()
|
||||
{
|
||||
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>
|
||||
/// List items to refresh.
|
||||
/// </summary>
|
||||
/// <param name="date">The upper limit for the refresh date.</param>
|
||||
/// <returns>The items that should be refreshed before the given date</returns>
|
||||
[HttpGet("/refreshables")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Task<ICollection<RefreshableItem>> GetAllPaths([FromQuery] DateTime? date)
|
||||
{
|
||||
|
@ -45,10 +45,11 @@ if __name__ == "__main__":
|
||||
async with ClientSession() as client:
|
||||
xem = TheXemClient(client)
|
||||
|
||||
advanced = any(x == "-a" for x in sys.argv)
|
||||
ret = guessit(
|
||||
sys.argv[1],
|
||||
xem_titles=await xem.get_expected_titles(),
|
||||
# extra_flags={"advanced": True},
|
||||
extra_flags={"advanced": advanced},
|
||||
)
|
||||
print(json.dumps(ret, cls=GuessitEncoder, indent=4))
|
||||
|
||||
|
@ -54,7 +54,7 @@ class UnlistTitles(Rule):
|
||||
consequence = [RemoveMatch, AppendMatch]
|
||||
|
||||
def when(self, matches: Matches, context) -> Any:
|
||||
titles: List[Match] = matches.named("title") # type: ignore
|
||||
titles: List[Match] = matches.named("title", lambda x: x.tagged("title")) # type: ignore
|
||||
|
||||
if not titles or len(titles) <= 1:
|
||||
return
|
||||
|
@ -104,30 +104,17 @@ class KyooClient:
|
||||
async def delete(
|
||||
self,
|
||||
path: str,
|
||||
type: Literal["episode", "movie"] | None = None,
|
||||
):
|
||||
logger.info("Deleting %s", path)
|
||||
|
||||
if type is None or type == "movie":
|
||||
async with self.client.delete(
|
||||
f'{self._url}/movies?filter=path eq "{quote(path)}"',
|
||||
f"{self._url}/paths?recursive=true&path={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()
|
||||
|
||||
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 with self.client.get(
|
||||
f"{self._url}/{path}",
|
||||
|
@ -14,7 +14,7 @@ async def main():
|
||||
async with Publisher() as publisher, KyooClient() as client:
|
||||
path = os.environ.get("SCANNER_LIBRARY_ROOT", "/video")
|
||||
await asyncio.gather(
|
||||
monitor(path, publisher),
|
||||
scan(path, publisher, client),
|
||||
monitor(path, publisher, client),
|
||||
scan(path, publisher, client, remove_deleted=True),
|
||||
refresh(publisher, client),
|
||||
)
|
||||
|
@ -1,15 +1,20 @@
|
||||
from logging import getLogger
|
||||
from os.path import isdir
|
||||
from watchfiles import awatch, Change
|
||||
|
||||
from .publisher import Publisher
|
||||
from .scanner import scan
|
||||
from providers.kyoo_client import KyooClient
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
async def monitor(path: str, publisher: Publisher):
|
||||
async def monitor(path: str, publisher: Publisher, client: KyooClient):
|
||||
async for changes in awatch(path, ignore_permission_denied=True):
|
||||
for event, file in changes:
|
||||
if event == Change.added:
|
||||
if isdir(file):
|
||||
await scan(file, publisher, client)
|
||||
else:
|
||||
await publisher.add(file)
|
||||
elif event == Change.deleted:
|
||||
await publisher.delete(file)
|
||||
|
@ -9,7 +9,9 @@ from providers.kyoo_client import KyooClient
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
async def scan(path: str, publisher: Publisher, client: KyooClient):
|
||||
async def scan(
|
||||
path: str, publisher: Publisher, client: KyooClient, remove_deleted=False
|
||||
):
|
||||
logger.info("Starting the scan. It can take some times...")
|
||||
ignore_pattern = None
|
||||
try:
|
||||
@ -25,12 +27,13 @@ async def scan(path: str, publisher: Publisher, client: KyooClient):
|
||||
to_register = [
|
||||
p for p in videos if p not in registered and not ignore_pattern.match(p)
|
||||
]
|
||||
deleted = [x for x in registered if x not in videos]
|
||||
|
||||
if remove_deleted:
|
||||
deleted = [x for x in registered if x not in videos]
|
||||
if len(deleted) != len(registered):
|
||||
await asyncio.gather(*map(publisher.delete, deleted))
|
||||
elif len(deleted) > 0:
|
||||
logger.warning("All video files are unavailable. Check your disks.")
|
||||
|
||||
await asyncio.gather(*map(publisher.add, to_register))
|
||||
logger.info("Scan finished.")
|
||||
logger.info(f"Scan finished for {path}.")
|
||||
|
Loading…
x
Reference in New Issue
Block a user