mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
feat: add wildcard glob support to file manager and transfer history search (#5767)
This commit is contained in:
@@ -235,6 +235,14 @@ async def delete_download_history(
|
|||||||
return schemas.Response(success=True)
|
return schemas.Response(success=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _glob_to_like(pattern: str) -> str:
|
||||||
|
"""
|
||||||
|
将 glob 通配符模式转换为 SQL LIKE 模式(使用 \\ 作为转义字符)
|
||||||
|
"""
|
||||||
|
result = pattern.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
||||||
|
return result.replace("*", "%").replace("?", "_")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/transfer", summary="查询整理记录", response_model=schemas.Response)
|
@router.get("/transfer", summary="查询整理记录", response_model=schemas.Response)
|
||||||
async def transfer_history(
|
async def transfer_history(
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
@@ -245,7 +253,7 @@ async def transfer_history(
|
|||||||
_: schemas.TokenPayload = Depends(verify_token),
|
_: schemas.TokenPayload = Depends(verify_token),
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
查询整理记录
|
查询整理记录,title 支持通配符 * 和 ?(如 *.mkv、*2024*)
|
||||||
"""
|
"""
|
||||||
if title == "失败":
|
if title == "失败":
|
||||||
title = None
|
title = None
|
||||||
@@ -255,14 +263,23 @@ async def transfer_history(
|
|||||||
status = True
|
status = True
|
||||||
|
|
||||||
if title:
|
if title:
|
||||||
words = jieba.cut(title, HMM=False)
|
if "*" in title or "?" in title:
|
||||||
title = "%".join(words)
|
like_pattern = _glob_to_like(title)
|
||||||
total = await TransferHistory.async_count_by_title(
|
total = await TransferHistory.async_count_by_title(
|
||||||
db, title=title, status=status
|
db, title=like_pattern, status=status, wildcard=True
|
||||||
)
|
)
|
||||||
result = await TransferHistory.async_list_by_title(
|
result = await TransferHistory.async_list_by_title(
|
||||||
db, title=title, page=page, count=count, status=status
|
db, title=like_pattern, page=page, count=count, status=status, wildcard=True
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
words = jieba.cut(title, HMM=False)
|
||||||
|
like_pattern = "%".join(words)
|
||||||
|
total = await TransferHistory.async_count_by_title(
|
||||||
|
db, title=like_pattern, status=status
|
||||||
|
)
|
||||||
|
result = await TransferHistory.async_list_by_title(
|
||||||
|
db, title=like_pattern, page=page, count=count, status=status
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
result = await TransferHistory.async_list_by_page(
|
result = await TransferHistory.async_list_by_page(
|
||||||
db, page=page, count=count, status=status
|
db, page=page, count=count, status=status
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import fnmatch
|
||||||
import math
|
import math
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
@@ -88,17 +90,22 @@ def reset(name: str, _: User = Depends(get_current_active_superuser)) -> Any:
|
|||||||
def list_files(
|
def list_files(
|
||||||
fileitem: schemas.FileItem,
|
fileitem: schemas.FileItem,
|
||||||
sort: Optional[str] = "updated_at",
|
sort: Optional[str] = "updated_at",
|
||||||
|
keyword: Optional[str] = None,
|
||||||
_: User = Depends(get_current_active_superuser),
|
_: User = Depends(get_current_active_superuser),
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
查询当前目录下所有目录和文件
|
查询当前目录下所有目录和文件
|
||||||
:param fileitem: 文件项
|
:param fileitem: 文件项
|
||||||
:param sort: 排序方式,name:按名称排序,time:按修改时间排序
|
:param sort: 排序方式,name:按名称排序,time:按修改时间排序
|
||||||
|
:param keyword: 通配符过滤,支持 * 和 ?,如 *.mkv、movie?.*
|
||||||
:param _: token
|
:param _: token
|
||||||
:return: 所有目录和文件
|
:return: 所有目录和文件
|
||||||
"""
|
"""
|
||||||
file_list = StorageChain().list_files(fileitem)
|
file_list = StorageChain().list_files(fileitem)
|
||||||
if file_list:
|
if file_list:
|
||||||
|
if keyword:
|
||||||
|
_pat = re.compile(fnmatch.translate(keyword), re.IGNORECASE)
|
||||||
|
file_list = [f for f in file_list if _pat.match(f.name or "")]
|
||||||
if sort == "name":
|
if sort == "name":
|
||||||
file_list.sort(key=lambda x: StringUtils.natural_sort_key(x.name or ""))
|
file_list.sort(key=lambda x: StringUtils.natural_sort_key(x.name or ""))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -68,21 +68,23 @@ class TransferHistory(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@db_query
|
@db_query
|
||||||
def list_by_title(cls, db: Session, title: str, page: Optional[int] = 1, count: Optional[int] = 30,
|
def list_by_title(cls, db: Session, title: str, page: Optional[int] = 1, count: Optional[int] = 30,
|
||||||
status: bool = None):
|
status: bool = None, wildcard: bool = False):
|
||||||
if status is not None:
|
if wildcard:
|
||||||
query = db.query(cls).filter(
|
text_filter = or_(
|
||||||
cls.status == status
|
cls.title.like(title, escape='\\'),
|
||||||
).order_by(
|
cls.src.like(title, escape='\\'),
|
||||||
cls.date.desc()
|
cls.dest.like(title, escape='\\'),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
query = db.query(cls).filter(or_(
|
text_filter = or_(
|
||||||
cls.title.like(f'%{title}%'),
|
cls.title.like(f'%{title}%'),
|
||||||
cls.src.like(f'%{title}%'),
|
cls.src.like(f'%{title}%'),
|
||||||
cls.dest.like(f'%{title}%'),
|
cls.dest.like(f'%{title}%'),
|
||||||
)).order_by(
|
|
||||||
cls.date.desc()
|
|
||||||
)
|
)
|
||||||
|
query = db.query(cls).filter(text_filter)
|
||||||
|
if status is not None:
|
||||||
|
query = query.filter(cls.status == status)
|
||||||
|
query = query.order_by(cls.date.desc())
|
||||||
|
|
||||||
# 当count为负数时,不限制页数查询所有
|
# 当count为负数时,不限制页数查询所有
|
||||||
if count >= 0:
|
if count >= 0:
|
||||||
@@ -93,21 +95,23 @@ class TransferHistory(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@async_db_query
|
@async_db_query
|
||||||
async def async_list_by_title(cls, db: AsyncSession, title: str, page: Optional[int] = 1, count: Optional[int] = 30,
|
async def async_list_by_title(cls, db: AsyncSession, title: str, page: Optional[int] = 1, count: Optional[int] = 30,
|
||||||
status: bool = None):
|
status: bool = None, wildcard: bool = False):
|
||||||
if status is not None:
|
if wildcard:
|
||||||
query = select(cls).filter(
|
text_filter = or_(
|
||||||
cls.status == status
|
cls.title.like(title, escape='\\'),
|
||||||
).order_by(
|
cls.src.like(title, escape='\\'),
|
||||||
cls.date.desc()
|
cls.dest.like(title, escape='\\'),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
query = select(cls).filter(or_(
|
text_filter = or_(
|
||||||
cls.title.like(f'%{title}%'),
|
cls.title.like(f'%{title}%'),
|
||||||
cls.src.like(f'%{title}%'),
|
cls.src.like(f'%{title}%'),
|
||||||
cls.dest.like(f'%{title}%'),
|
cls.dest.like(f'%{title}%'),
|
||||||
)).order_by(
|
|
||||||
cls.date.desc()
|
|
||||||
)
|
)
|
||||||
|
query = select(cls).filter(text_filter)
|
||||||
|
if status is not None:
|
||||||
|
query = query.filter(cls.status == status)
|
||||||
|
query = query.order_by(cls.date.desc())
|
||||||
|
|
||||||
# 当count为负数时,不限制页数查询所有
|
# 当count为负数时,不限制页数查询所有
|
||||||
if count >= 0:
|
if count >= 0:
|
||||||
@@ -232,31 +236,43 @@ class TransferHistory(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@db_query
|
@db_query
|
||||||
def count_by_title(cls, db: Session, title: str, status: bool = None):
|
def count_by_title(cls, db: Session, title: str, status: bool = None, wildcard: bool = False):
|
||||||
if status is not None:
|
if wildcard:
|
||||||
return db.query(func.count(cls.id)).filter(cls.status == status).first()[0]
|
text_filter = or_(
|
||||||
|
cls.title.like(title, escape='\\'),
|
||||||
|
cls.src.like(title, escape='\\'),
|
||||||
|
cls.dest.like(title, escape='\\'),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return db.query(func.count(cls.id)).filter(or_(
|
text_filter = or_(
|
||||||
cls.title.like(f'%{title}%'),
|
cls.title.like(f'%{title}%'),
|
||||||
cls.src.like(f'%{title}%'),
|
cls.src.like(f'%{title}%'),
|
||||||
cls.dest.like(f'%{title}%')
|
cls.dest.like(f'%{title}%'),
|
||||||
)).first()[0]
|
)
|
||||||
|
query = db.query(func.count(cls.id)).filter(text_filter)
|
||||||
|
if status is not None:
|
||||||
|
query = query.filter(cls.status == status)
|
||||||
|
return query.first()[0]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_db_query
|
@async_db_query
|
||||||
async def async_count_by_title(cls, db: AsyncSession, title: str, status: bool = None):
|
async def async_count_by_title(cls, db: AsyncSession, title: str, status: bool = None, wildcard: bool = False):
|
||||||
if status is not None:
|
if wildcard:
|
||||||
result = await db.execute(
|
text_filter = or_(
|
||||||
select(func.count(cls.id)).filter(cls.status == status)
|
cls.title.like(title, escape='\\'),
|
||||||
|
cls.src.like(title, escape='\\'),
|
||||||
|
cls.dest.like(title, escape='\\'),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = await db.execute(
|
text_filter = or_(
|
||||||
select(func.count(cls.id)).filter(or_(
|
cls.title.like(f'%{title}%'),
|
||||||
cls.title.like(f'%{title}%'),
|
cls.src.like(f'%{title}%'),
|
||||||
cls.src.like(f'%{title}%'),
|
cls.dest.like(f'%{title}%'),
|
||||||
cls.dest.like(f'%{title}%')
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
|
stmt = select(func.count(cls.id)).filter(text_filter)
|
||||||
|
if status is not None:
|
||||||
|
stmt = stmt.filter(cls.status == status)
|
||||||
|
result = await db.execute(stmt)
|
||||||
return result.scalar()
|
return result.scalar()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user