feat: add wildcard glob support to file manager and transfer history search (#5767)

This commit is contained in:
DDSRem
2026-05-13 21:08:51 +08:00
committed by GitHub
parent 7b1ece8b83
commit e5b67438d9
3 changed files with 87 additions and 47 deletions

View File

@@ -235,6 +235,14 @@ async def delete_download_history(
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)
async def transfer_history(
title: Optional[str] = None,
@@ -245,7 +253,7 @@ async def transfer_history(
_: schemas.TokenPayload = Depends(verify_token),
) -> Any:
"""
查询整理记录
查询整理记录title 支持通配符 * 和 ?(如 *.mkv、*2024*
"""
if title == "失败":
title = None
@@ -255,14 +263,23 @@ async def transfer_history(
status = True
if title:
words = jieba.cut(title, HMM=False)
title = "%".join(words)
total = await TransferHistory.async_count_by_title(
db, title=title, status=status
)
result = await TransferHistory.async_list_by_title(
db, title=title, page=page, count=count, status=status
)
if "*" in title or "?" in title:
like_pattern = _glob_to_like(title)
total = await TransferHistory.async_count_by_title(
db, title=like_pattern, status=status, wildcard=True
)
result = await TransferHistory.async_list_by_title(
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:
result = await TransferHistory.async_list_by_page(
db, page=page, count=count, status=status

View File

@@ -1,4 +1,6 @@
import fnmatch
import math
import re
from pathlib import Path
from typing import Any, List, Optional
@@ -88,17 +90,22 @@ def reset(name: str, _: User = Depends(get_current_active_superuser)) -> Any:
def list_files(
fileitem: schemas.FileItem,
sort: Optional[str] = "updated_at",
keyword: Optional[str] = None,
_: User = Depends(get_current_active_superuser),
) -> Any:
"""
查询当前目录下所有目录和文件
:param fileitem: 文件项
:param sort: 排序方式name:按名称排序time:按修改时间排序
:param keyword: 通配符过滤,支持 * 和 ?,如 *.mkv、movie?.*
:param _: token
:return: 所有目录和文件
"""
file_list = StorageChain().list_files(fileitem)
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":
file_list.sort(key=lambda x: StringUtils.natural_sort_key(x.name or ""))
else:

View File

@@ -68,51 +68,55 @@ class TransferHistory(Base):
@classmethod
@db_query
def list_by_title(cls, db: Session, title: str, page: Optional[int] = 1, count: Optional[int] = 30,
status: bool = None):
if status is not None:
query = db.query(cls).filter(
cls.status == status
).order_by(
cls.date.desc()
status: bool = None, wildcard: bool = False):
if wildcard:
text_filter = or_(
cls.title.like(title, escape='\\'),
cls.src.like(title, escape='\\'),
cls.dest.like(title, escape='\\'),
)
else:
query = db.query(cls).filter(or_(
text_filter = or_(
cls.title.like(f'%{title}%'),
cls.src.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为负数时不限制页数查询所有
if count >= 0:
query = query.offset((page - 1) * count).limit(count)
return query.all()
@classmethod
@async_db_query
async def async_list_by_title(cls, db: AsyncSession, title: str, page: Optional[int] = 1, count: Optional[int] = 30,
status: bool = None):
if status is not None:
query = select(cls).filter(
cls.status == status
).order_by(
cls.date.desc()
status: bool = None, wildcard: bool = False):
if wildcard:
text_filter = or_(
cls.title.like(title, escape='\\'),
cls.src.like(title, escape='\\'),
cls.dest.like(title, escape='\\'),
)
else:
query = select(cls).filter(or_(
text_filter = or_(
cls.title.like(f'%{title}%'),
cls.src.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为负数时不限制页数查询所有
if count >= 0:
query = query.offset((page - 1) * count).limit(count)
result = await db.execute(query)
return result.scalars().all()
@@ -232,31 +236,43 @@ class TransferHistory(Base):
@classmethod
@db_query
def count_by_title(cls, db: Session, title: str, status: bool = None):
if status is not None:
return db.query(func.count(cls.id)).filter(cls.status == status).first()[0]
def count_by_title(cls, db: Session, title: str, status: bool = None, wildcard: bool = False):
if wildcard:
text_filter = or_(
cls.title.like(title, escape='\\'),
cls.src.like(title, escape='\\'),
cls.dest.like(title, escape='\\'),
)
else:
return db.query(func.count(cls.id)).filter(or_(
text_filter = or_(
cls.title.like(f'%{title}%'),
cls.src.like(f'%{title}%'),
cls.dest.like(f'%{title}%')
)).first()[0]
cls.dest.like(f'%{title}%'),
)
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
@async_db_query
async def async_count_by_title(cls, db: AsyncSession, title: str, status: bool = None):
if status is not None:
result = await db.execute(
select(func.count(cls.id)).filter(cls.status == status)
async def async_count_by_title(cls, db: AsyncSession, title: str, status: bool = None, wildcard: bool = False):
if wildcard:
text_filter = or_(
cls.title.like(title, escape='\\'),
cls.src.like(title, escape='\\'),
cls.dest.like(title, escape='\\'),
)
else:
result = await db.execute(
select(func.count(cls.id)).filter(or_(
cls.title.like(f'%{title}%'),
cls.src.like(f'%{title}%'),
cls.dest.like(f'%{title}%')
))
text_filter = or_(
cls.title.like(f'%{title}%'),
cls.src.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()
@classmethod