mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-08 17:08:35 +08:00
feat:整理手动中止功能
This commit is contained in:
@@ -8,7 +8,7 @@ from app import schemas
|
|||||||
from app.chain.media import MediaChain
|
from app.chain.media import MediaChain
|
||||||
from app.chain.storage import StorageChain
|
from app.chain.storage import StorageChain
|
||||||
from app.chain.transfer import TransferChain
|
from app.chain.transfer import TransferChain
|
||||||
from app.core.config import settings
|
from app.core.config import settings, global_vars
|
||||||
from app.core.metainfo import MetaInfoPath
|
from app.core.metainfo import MetaInfoPath
|
||||||
from app.core.security import verify_token, verify_apitoken
|
from app.core.security import verify_token, verify_apitoken
|
||||||
from app.db import get_db
|
from app.db import get_db
|
||||||
@@ -75,6 +75,8 @@ async def remove_queue(fileitem: schemas.FileItem, _: schemas.TokenPayload = Dep
|
|||||||
:param _: Token校验
|
:param _: Token校验
|
||||||
"""
|
"""
|
||||||
TransferChain().remove_from_queue(fileitem)
|
TransferChain().remove_from_queue(fileitem)
|
||||||
|
# 取消整理
|
||||||
|
global_vars.stop_transfer(fileitem.path)
|
||||||
return schemas.Response(success=True)
|
return schemas.Response(success=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+21
-1
@@ -798,6 +798,8 @@ class GlobalVar(object):
|
|||||||
SUBSCRIPTIONS: List[dict] = []
|
SUBSCRIPTIONS: List[dict] = []
|
||||||
# 需应急停止的工作流
|
# 需应急停止的工作流
|
||||||
EMERGENCY_STOP_WORKFLOWS: List[int] = []
|
EMERGENCY_STOP_WORKFLOWS: List[int] = []
|
||||||
|
# 需应急停止文件整理
|
||||||
|
EMERGENCY_STOP_TRANSFER: List[str] = []
|
||||||
|
|
||||||
def stop_system(self):
|
def stop_system(self):
|
||||||
"""
|
"""
|
||||||
@@ -838,12 +840,30 @@ class GlobalVar(object):
|
|||||||
if workflow_id in self.EMERGENCY_STOP_WORKFLOWS:
|
if workflow_id in self.EMERGENCY_STOP_WORKFLOWS:
|
||||||
self.EMERGENCY_STOP_WORKFLOWS.remove(workflow_id)
|
self.EMERGENCY_STOP_WORKFLOWS.remove(workflow_id)
|
||||||
|
|
||||||
def is_workflow_stopped(self, workflow_id: int):
|
def is_workflow_stopped(self, workflow_id: int) -> bool:
|
||||||
"""
|
"""
|
||||||
是否停止工作流
|
是否停止工作流
|
||||||
"""
|
"""
|
||||||
return self.is_system_stopped or workflow_id in self.EMERGENCY_STOP_WORKFLOWS
|
return self.is_system_stopped or workflow_id in self.EMERGENCY_STOP_WORKFLOWS
|
||||||
|
|
||||||
|
def stop_transfer(self, path: str):
|
||||||
|
"""
|
||||||
|
停止文件整理
|
||||||
|
"""
|
||||||
|
if path not in self.EMERGENCY_STOP_TRANSFER:
|
||||||
|
self.EMERGENCY_STOP_TRANSFER.append(path)
|
||||||
|
|
||||||
|
def is_transfer_stopped(self, path: str) -> bool:
|
||||||
|
"""
|
||||||
|
是否停止文件整理
|
||||||
|
"""
|
||||||
|
if self.is_system_stopped:
|
||||||
|
return True
|
||||||
|
if path in self.EMERGENCY_STOP_TRANSFER:
|
||||||
|
self.EMERGENCY_STOP_TRANSFER.remove(path)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# 全局标识
|
# 全局标识
|
||||||
global_vars = GlobalVar()
|
global_vars = GlobalVar()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from typing import List, Optional, Tuple, Union
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.core.config import settings
|
from app.core.config import settings, global_vars
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules.filemanager import StorageBase
|
from app.modules.filemanager import StorageBase
|
||||||
from app.modules.filemanager.storages import transfer_process
|
from app.modules.filemanager.storages import transfer_process
|
||||||
@@ -628,9 +628,12 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
|
|||||||
uploaded_size = 0
|
uploaded_size = 0
|
||||||
with open(local_path, 'rb') as f:
|
with open(local_path, 'rb') as f:
|
||||||
for part_info in part_info_list:
|
for part_info in part_info_list:
|
||||||
part_num = part_info['part_number']
|
if global_vars.is_transfer_stopped(local_path.as_posix()):
|
||||||
|
logger.info(f"【阿里云盘】{target_name} 上传已取消!")
|
||||||
|
return None
|
||||||
|
|
||||||
# 计算分片参数
|
# 计算分片参数
|
||||||
|
part_num = part_info['part_number']
|
||||||
start = (part_num - 1) * chunk_size
|
start = (part_num - 1) * chunk_size
|
||||||
end = min(start + chunk_size, file_size)
|
end = min(start + chunk_size, file_size)
|
||||||
current_chunk_size = end - start
|
current_chunk_size = end - start
|
||||||
@@ -656,7 +659,6 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
|
|||||||
upload_url = new_urls[0]['upload_url']
|
upload_url = new_urls[0]['upload_url']
|
||||||
else:
|
else:
|
||||||
upload_url = part_info['upload_url']
|
upload_url = part_info['upload_url']
|
||||||
|
|
||||||
# 执行上传
|
# 执行上传
|
||||||
logger.info(
|
logger.info(
|
||||||
f"【阿里云盘】开始 第{attempt + 1}次 上传 {target_name} 分片 {part_num} ...")
|
f"【阿里云盘】开始 第{attempt + 1}次 上传 {target_name} 分片 {part_num} ...")
|
||||||
@@ -725,14 +727,15 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
|
|||||||
with requests.get(download_url, stream=True) as r:
|
with requests.get(download_url, stream=True) as r:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
downloaded_size = 0
|
downloaded_size = 0
|
||||||
|
|
||||||
with open(local_path, "wb") as f:
|
with open(local_path, "wb") as f:
|
||||||
for chunk in r.iter_content(chunk_size=self.chunk_size):
|
for chunk in r.iter_content(chunk_size=self.chunk_size):
|
||||||
|
if global_vars.is_transfer_stopped(fileitem.path):
|
||||||
|
logger.info(f"【阿里云盘】{fileitem.path} 下载已取消!")
|
||||||
|
return None
|
||||||
if chunk:
|
if chunk:
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
downloaded_size += len(chunk)
|
|
||||||
|
|
||||||
# 更新进度
|
# 更新进度
|
||||||
|
downloaded_size += len(chunk)
|
||||||
if file_size:
|
if file_size:
|
||||||
progress = (downloaded_size * 100) / file_size
|
progress = (downloaded_size * 100) / file_size
|
||||||
progress_callback(progress)
|
progress_callback(progress)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import requests
|
|||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.core.cache import cached
|
from app.core.cache import cached
|
||||||
from app.core.config import settings
|
from app.core.config import settings, global_vars
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules.filemanager.storages import StorageBase, transfer_process
|
from app.modules.filemanager.storages import StorageBase, transfer_process
|
||||||
from app.schemas.types import StorageSchema
|
from app.schemas.types import StorageSchema
|
||||||
@@ -561,6 +561,9 @@ class Alist(StorageBase, metaclass=WeakSingleton):
|
|||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
with open(local_path, "wb") as f:
|
with open(local_path, "wb") as f:
|
||||||
for chunk in r.iter_content(chunk_size=8192):
|
for chunk in r.iter_content(chunk_size=8192):
|
||||||
|
if global_vars.is_transfer_stopped(fileitem.path):
|
||||||
|
logger.info(f"【OpenList】{fileitem.path} 下载已取消!")
|
||||||
|
return None
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
|
|
||||||
if local_path.exists():
|
if local_path.exists():
|
||||||
@@ -601,11 +604,14 @@ class Alist(StorageBase, metaclass=WeakSingleton):
|
|||||||
self.file_size = file_path.stat().st_size
|
self.file_size = file_path.stat().st_size
|
||||||
|
|
||||||
def read(self, size=-1):
|
def read(self, size=-1):
|
||||||
|
if global_vars.is_transfer_stopped(path.as_posix()):
|
||||||
|
logger.info(f"【OpenList】{path} 上传已取消!")
|
||||||
|
return None
|
||||||
chunk = self.file.read(size)
|
chunk = self.file.read(size)
|
||||||
if chunk:
|
if chunk:
|
||||||
self.uploaded_size += len(chunk)
|
self.uploaded_size += len(chunk)
|
||||||
if self.callback:
|
if self.callback:
|
||||||
percent = (self.uploaded_size* 100) / self.file_size
|
percent = (self.uploaded_size * 100) / self.file_size
|
||||||
self.callback(percent)
|
self.callback(percent)
|
||||||
return chunk
|
return chunk
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from pathlib import Path
|
|||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
|
from app.core.config import global_vars
|
||||||
from app.helper.directory import DirectoryHelper
|
from app.helper.directory import DirectoryHelper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules.filemanager.storages import StorageBase, transfer_process
|
from app.modules.filemanager.storages import StorageBase, transfer_process
|
||||||
@@ -98,7 +99,7 @@ class LocalStorage(StorageBase):
|
|||||||
# 遍历目录
|
# 遍历目录
|
||||||
path_obj = Path(path)
|
path_obj = Path(path)
|
||||||
if not path_obj.exists():
|
if not path_obj.exists():
|
||||||
logger.warn(f"【local】目录不存在:{path}")
|
logger.warn(f"【本地】目录不存在:{path}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 如果是文件
|
# 如果是文件
|
||||||
@@ -170,7 +171,7 @@ class LocalStorage(StorageBase):
|
|||||||
else:
|
else:
|
||||||
shutil.rmtree(path_obj, ignore_errors=True)
|
shutil.rmtree(path_obj, ignore_errors=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"【local】删除文件失败:{e}")
|
logger.error(f"【本地】删除文件失败:{e}")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ class LocalStorage(StorageBase):
|
|||||||
try:
|
try:
|
||||||
path_obj.rename(path_obj.parent / name)
|
path_obj.rename(path_obj.parent / name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"【local】重命名文件失败:{e}")
|
logger.error(f"【本地】重命名文件失败:{e}")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -204,6 +205,9 @@ class LocalStorage(StorageBase):
|
|||||||
try:
|
try:
|
||||||
with open(src, "rb") as fsrc, open(dest, "wb") as fdst:
|
with open(src, "rb") as fsrc, open(dest, "wb") as fdst:
|
||||||
while True:
|
while True:
|
||||||
|
if global_vars.is_transfer_stopped(src.as_posix()):
|
||||||
|
logger.info(f"【本地】{src} 复制已取消!")
|
||||||
|
return False
|
||||||
buf = fsrc.read(self.chunk_size)
|
buf = fsrc.read(self.chunk_size)
|
||||||
if not buf:
|
if not buf:
|
||||||
break
|
break
|
||||||
@@ -217,7 +221,7 @@ class LocalStorage(StorageBase):
|
|||||||
shutil.copystat(src, dest)
|
shutil.copystat(src, dest)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"【local】复制文件 {src} 失败:{e}")
|
logger.error(f"【本地】复制文件 {src} 失败:{e}")
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
progress_callback(100)
|
progress_callback(100)
|
||||||
@@ -239,7 +243,7 @@ class LocalStorage(StorageBase):
|
|||||||
path.unlink()
|
path.unlink()
|
||||||
return self.get_item(target_path)
|
return self.get_item(target_path)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"【local】移动文件失败:{err}")
|
logger.error(f"【本地】移动文件失败:{err}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def copy(
|
def copy(
|
||||||
@@ -257,7 +261,7 @@ class LocalStorage(StorageBase):
|
|||||||
if self._copy_with_progress(src, dest):
|
if self._copy_with_progress(src, dest):
|
||||||
return True
|
return True
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"【local】复制文件失败:{err}")
|
logger.error(f"【本地】复制文件失败:{err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def move(
|
def move(
|
||||||
@@ -277,7 +281,7 @@ class LocalStorage(StorageBase):
|
|||||||
src.unlink()
|
src.unlink()
|
||||||
return True
|
return True
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"【local】移动文件失败:{err}")
|
logger.error(f"【本地】移动文件失败:{err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
@@ -287,7 +291,7 @@ class LocalStorage(StorageBase):
|
|||||||
file_path = Path(fileitem.path)
|
file_path = Path(fileitem.path)
|
||||||
code, message = SystemUtils.link(file_path, target_file)
|
code, message = SystemUtils.link(file_path, target_file)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
logger.error(f"【local】硬链接文件失败:{message}")
|
logger.error(f"【本地】硬链接文件失败:{message}")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -298,7 +302,7 @@ class LocalStorage(StorageBase):
|
|||||||
file_path = Path(fileitem.path)
|
file_path = Path(fileitem.path)
|
||||||
code, message = SystemUtils.softlink(file_path, target_file)
|
code, message = SystemUtils.softlink(file_path, target_file)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
logger.error(f"【local】软链接文件失败:{message}")
|
logger.error(f"【本地】软链接文件失败:{message}")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from smbclient import ClientConfig, register_session, reset_connection_cache
|
|||||||
from smbprotocol.exceptions import SMBException, SMBResponseException, SMBAuthenticationError
|
from smbprotocol.exceptions import SMBException, SMBResponseException, SMBAuthenticationError
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.core.config import settings
|
from app.core.config import settings, global_vars
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules.filemanager import StorageBase
|
from app.modules.filemanager import StorageBase
|
||||||
from app.modules.filemanager.storages import transfer_process
|
from app.modules.filemanager.storages import transfer_process
|
||||||
@@ -438,12 +438,14 @@ class SMB(StorageBase, metaclass=WeakSingleton):
|
|||||||
with open(local_path, "wb") as dst_file:
|
with open(local_path, "wb") as dst_file:
|
||||||
downloaded_size = 0
|
downloaded_size = 0
|
||||||
while True:
|
while True:
|
||||||
|
if global_vars.is_transfer_stopped(fileitem.path):
|
||||||
|
logger.info(f"【SMB】{fileitem.path} 下载已取消!")
|
||||||
|
return None
|
||||||
chunk = src_file.read(self.chunk_size)
|
chunk = src_file.read(self.chunk_size)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
dst_file.write(chunk)
|
dst_file.write(chunk)
|
||||||
downloaded_size += len(chunk)
|
downloaded_size += len(chunk)
|
||||||
|
|
||||||
# 更新进度
|
# 更新进度
|
||||||
if file_size:
|
if file_size:
|
||||||
progress = (downloaded_size * 100) / file_size
|
progress = (downloaded_size * 100) / file_size
|
||||||
@@ -485,12 +487,14 @@ class SMB(StorageBase, metaclass=WeakSingleton):
|
|||||||
with smbclient.open_file(smb_path, mode="wb") as dst_file:
|
with smbclient.open_file(smb_path, mode="wb") as dst_file:
|
||||||
uploaded_size = 0
|
uploaded_size = 0
|
||||||
while True:
|
while True:
|
||||||
|
if global_vars.is_transfer_stopped(path.as_posix()):
|
||||||
|
logger.info(f"【SMB】{path} 上传已取消!")
|
||||||
|
return None
|
||||||
chunk = src_file.read(self.chunk_size)
|
chunk = src_file.read(self.chunk_size)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
dst_file.write(chunk)
|
dst_file.write(chunk)
|
||||||
uploaded_size += len(chunk)
|
uploaded_size += len(chunk)
|
||||||
|
|
||||||
# 更新进度
|
# 更新进度
|
||||||
if file_size:
|
if file_size:
|
||||||
progress = (uploaded_size * 100) / file_size
|
progress = (uploaded_size * 100) / file_size
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from oss2 import SizedFileAdapter, determine_part_size
|
|||||||
from oss2.models import PartInfo
|
from oss2.models import PartInfo
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.core.config import settings
|
from app.core.config import settings, global_vars
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules.filemanager import StorageBase
|
from app.modules.filemanager import StorageBase
|
||||||
from app.modules.filemanager.storages import transfer_process
|
from app.modules.filemanager.storages import transfer_process
|
||||||
@@ -532,6 +532,9 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
|
|||||||
part_number = 1
|
part_number = 1
|
||||||
offset = 0
|
offset = 0
|
||||||
while offset < file_size:
|
while offset < file_size:
|
||||||
|
if global_vars.is_transfer_stopped(local_path.as_posix()):
|
||||||
|
logger.info(f"【115】{local_path} 上传已取消!")
|
||||||
|
return None
|
||||||
num_to_upload = min(part_size, file_size - offset)
|
num_to_upload = min(part_size, file_size - offset)
|
||||||
# 调用SizedFileAdapter(fileobj, size)方法会生成一个新的文件对象,重新计算起始追加位置。
|
# 调用SizedFileAdapter(fileobj, size)方法会生成一个新的文件对象,重新计算起始追加位置。
|
||||||
logger.info(f"【115】开始上传 {target_name} 分片 {part_number}: {offset} -> {offset + num_to_upload}")
|
logger.info(f"【115】开始上传 {target_name} 分片 {part_number}: {offset} -> {offset + num_to_upload}")
|
||||||
@@ -614,10 +617,12 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
|
|||||||
|
|
||||||
with open(local_path, "wb") as f:
|
with open(local_path, "wb") as f:
|
||||||
for chunk in r.iter_content(chunk_size=self.chunk_size):
|
for chunk in r.iter_content(chunk_size=self.chunk_size):
|
||||||
|
if global_vars.is_transfer_stopped(fileitem.path):
|
||||||
|
logger.info(f"【115】{fileitem.path} 下载已取消!")
|
||||||
|
return None
|
||||||
if chunk:
|
if chunk:
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
downloaded_size += len(chunk)
|
downloaded_size += len(chunk)
|
||||||
|
|
||||||
# 更新进度
|
# 更新进度
|
||||||
if file_size:
|
if file_size:
|
||||||
progress = (downloaded_size * 100) / file_size
|
progress = (downloaded_size * 100) / file_size
|
||||||
|
|||||||
Reference in New Issue
Block a user