mirror of
https://github.com/RedDeadDepresso/KKAFIO.git
synced 2025-12-23 01:40:01 +00:00
feat: clear directory
This commit is contained in:
34
app/common/clear_worker.py
Normal file
34
app/common/clear_worker.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from send2trash import send2trash
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from PySide6.QtCore import QRunnable, Signal, QObject
|
||||||
|
|
||||||
|
|
||||||
|
class ClearSignalBus(QObject):
|
||||||
|
finishSignal = Signal(bool)
|
||||||
|
|
||||||
|
|
||||||
|
class ClearWorker(QRunnable):
|
||||||
|
def __init__(self, path: Path, deleteFolder=False) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.path = path
|
||||||
|
self.deleteFolder = deleteFolder
|
||||||
|
self.clearSignalBus = ClearSignalBus()
|
||||||
|
self.finishSignal = self.clearSignalBus.finishSignal
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
if self.deleteFolder:
|
||||||
|
send2trash(self.path)
|
||||||
|
else:
|
||||||
|
self.deleteContents()
|
||||||
|
self.finishSignal.emit(True)
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
self.finishSignal.emit(False)
|
||||||
|
|
||||||
|
def deleteContents(self):
|
||||||
|
for item in self.path.iterdir():
|
||||||
|
send2trash(item)
|
||||||
|
|
||||||
32
app/common/notification.py
Normal file
32
app/common/notification.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from PySide6.QtCore import Qt
|
||||||
|
from qfluentwidgets import InfoBar, InfoBarPosition
|
||||||
|
|
||||||
|
|
||||||
|
class Notification:
|
||||||
|
|
||||||
|
mainWindow = None
|
||||||
|
|
||||||
|
def success(self, title: str, content: str = ''):
|
||||||
|
InfoBar.success(
|
||||||
|
title=title,
|
||||||
|
content=content,
|
||||||
|
orient=Qt.Horizontal,
|
||||||
|
isClosable=True,
|
||||||
|
position=InfoBarPosition.TOP_RIGHT,
|
||||||
|
duration=2000,
|
||||||
|
parent=self.mainWindow
|
||||||
|
)
|
||||||
|
|
||||||
|
def error(self, title: str, content: str = ''):
|
||||||
|
InfoBar.error(
|
||||||
|
title=title,
|
||||||
|
content=content,
|
||||||
|
orient=Qt.Horizontal,
|
||||||
|
isClosable=True,
|
||||||
|
position=InfoBarPosition.TOP_RIGHT,
|
||||||
|
duration=2000,
|
||||||
|
parent=self.mainWindow
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
notification = Notification()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from PySide6.QtCore import QProcess, Signal, QObject, Slot
|
from PySide6.QtCore import QProcess, QObject, Slot
|
||||||
|
|
||||||
|
|
||||||
class ScriptManager(QObject):
|
class ScriptManager(QObject):
|
||||||
@@ -68,3 +68,6 @@ class ScriptManager(QObject):
|
|||||||
self._process = None
|
self._process = None
|
||||||
self.signalBus.stopSignal.emit()
|
self.signalBus.stopSignal.emit()
|
||||||
|
|
||||||
|
def scriptRunning(self):
|
||||||
|
return self._process is not None
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from PySide6.QtCore import QObject, Signal
|
from PySide6.QtCore import QObject, Signal, QThreadPool
|
||||||
from qfluentwidgets import SettingCardGroup
|
from qfluentwidgets import SettingCardGroup
|
||||||
from app.common.logger import Logger
|
from app.common.logger import Logger
|
||||||
from app.common.script_manager import ScriptManager
|
from app.common.script_manager import ScriptManager
|
||||||
|
|
||||||
|
|
||||||
class SignalBus(QObject):
|
class SignalBus(QObject):
|
||||||
""" Signal bus """
|
""" Signal bus """
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ class SignalBus(QObject):
|
|||||||
|
|
||||||
selectAllClicked = Signal()
|
selectAllClicked = Signal()
|
||||||
clearAllClicked = Signal()
|
clearAllClicked = Signal()
|
||||||
|
disableStartSignal = Signal(bool)
|
||||||
startSignal = Signal()
|
startSignal = Signal()
|
||||||
stopSignal = Signal()
|
stopSignal = Signal()
|
||||||
loggerSignal = Signal(str)
|
loggerSignal = Signal(str)
|
||||||
@@ -21,6 +23,9 @@ class SignalBus(QObject):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.logger = Logger(self)
|
self.logger = Logger(self)
|
||||||
self.scriptManager = ScriptManager(self)
|
self.scriptManager = ScriptManager(self)
|
||||||
|
self.threadPool = QThreadPool(self)
|
||||||
|
|
||||||
|
def scriptRunning(self) -> bool:
|
||||||
|
return self.scriptManager.scriptRunning()
|
||||||
|
|
||||||
signalBus = SignalBus()
|
signalBus = SignalBus()
|
||||||
35
app/components/clear_messagebox.py
Normal file
35
app/components/clear_messagebox.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from PySide6.QtCore import Qt, Signal
|
||||||
|
from qfluentwidgets import MessageBox, CheckBox
|
||||||
|
|
||||||
|
|
||||||
|
class ClearMessageBox(MessageBox):
|
||||||
|
mainWindow = None
|
||||||
|
yesSignal = Signal()
|
||||||
|
cancelSignal = Signal()
|
||||||
|
|
||||||
|
def __init__(self, title: str, content: str, parent=None):
|
||||||
|
parent = parent if parent is not None else self.mainWindow
|
||||||
|
if parent is None:
|
||||||
|
raise Exception("Please, assign mainWindow")
|
||||||
|
|
||||||
|
super().__init__(title, content, parent)
|
||||||
|
self.yesButtonPressed = False
|
||||||
|
self.deleteFolder = False
|
||||||
|
|
||||||
|
self.checkBox = CheckBox('Delete folder')
|
||||||
|
self.textLayout.insertWidget(2, self.checkBox, 0, alignment=Qt.AlignLeft)
|
||||||
|
|
||||||
|
self.__connectSignalToSlot()
|
||||||
|
|
||||||
|
def __connectSignalToSlot(self):
|
||||||
|
self.checkBox.checkStateChanged.connect(self.onChecked)
|
||||||
|
self.yesSignal.connect(self.onYesSignal)
|
||||||
|
|
||||||
|
def onYesSignal(self):
|
||||||
|
self.yesButtonPressed = True
|
||||||
|
|
||||||
|
def onChecked(self, value: bool):
|
||||||
|
self.deleteFolder = value
|
||||||
|
|
||||||
|
def getResponse(self) -> tuple[bool, bool]:
|
||||||
|
return self.yesButtonPressed, self.deleteFolder
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt, Slot
|
||||||
from PySide6.QtGui import QIcon
|
from PySide6.QtGui import QIcon
|
||||||
from PySide6.QtWidgets import QFileDialog
|
from PySide6.QtWidgets import QFileDialog
|
||||||
from qfluentwidgets import SettingCard, FluentIconBase, FluentIcon, CommandBar, Action, LineEdit, LineEditButton
|
from qfluentwidgets import SettingCard, FluentIconBase, FluentIcon, CommandBar, Action, LineEdit, LineEditButton
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from ..common.config import cfg
|
from app.common.notification import notification
|
||||||
|
from app.common.config import cfg
|
||||||
|
from app.common.signal_bus import signalBus
|
||||||
|
from app.common.clear_worker import ClearWorker
|
||||||
|
from app.components.clear_messagebox import ClearMessageBox
|
||||||
|
|
||||||
|
|
||||||
class FolderLineEdit(LineEdit):
|
class FolderLineEdit(LineEdit):
|
||||||
@@ -26,7 +29,9 @@ class FolderLineEdit(LineEdit):
|
|||||||
|
|
||||||
|
|
||||||
class FolderSettingCard(SettingCard):
|
class FolderSettingCard(SettingCard):
|
||||||
def __init__(self, configItem, icon: Union[str, QIcon, FluentIconBase], title, content=None, parent=None):
|
clearQueue = set()
|
||||||
|
|
||||||
|
def __init__(self, configItem, icon: Union[str, QIcon, FluentIconBase], titleGroup, title, content=None, parent=None, clearable=True):
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -46,9 +51,11 @@ class FolderSettingCard(SettingCard):
|
|||||||
parent widget
|
parent widget
|
||||||
"""
|
"""
|
||||||
super().__init__(icon, title, content, parent)
|
super().__init__(icon, title, content, parent)
|
||||||
|
self.titleGroup = titleGroup
|
||||||
self.configItem = configItem
|
self.configItem = configItem
|
||||||
self.lineEdit = FolderLineEdit()
|
self.lineEdit = FolderLineEdit()
|
||||||
self.lineEdit.setText(configItem.value)
|
self.lineEdit.setText(configItem.value)
|
||||||
|
self.clearable = clearable
|
||||||
|
|
||||||
self.__initCommandBar()
|
self.__initCommandBar()
|
||||||
self.__initLayout()
|
self.__initLayout()
|
||||||
@@ -56,13 +63,18 @@ class FolderSettingCard(SettingCard):
|
|||||||
|
|
||||||
def __initCommandBar(self):
|
def __initCommandBar(self):
|
||||||
self.commandBar = CommandBar()
|
self.commandBar = CommandBar()
|
||||||
explorerAction = Action(FluentIcon.FOLDER, 'Show in Explorer')
|
self.explorerAction = Action(FluentIcon.FOLDER, 'Show in Explorer')
|
||||||
explorerAction.triggered.connect(self.openExplorer)
|
self.explorerAction.triggered.connect(self.openExplorer)
|
||||||
self.commandBar.addHiddenAction(explorerAction)
|
self.commandBar.addHiddenAction(self.explorerAction)
|
||||||
|
|
||||||
browseAction = Action(FluentIcon.EDIT, 'Browse')
|
self.browseAction = Action(FluentIcon.EDIT, 'Browse')
|
||||||
browseAction.triggered.connect(self.browse)
|
self.browseAction.triggered.connect(self.browse)
|
||||||
self.commandBar.addHiddenAction(browseAction)
|
self.commandBar.addHiddenAction(self.browseAction)
|
||||||
|
|
||||||
|
if self.clearable:
|
||||||
|
self.clearAction = Action(FluentIcon.DELETE, 'Clear')
|
||||||
|
self.clearAction.triggered.connect(self.showClearDialog)
|
||||||
|
self.commandBar.addHiddenAction(self.clearAction)
|
||||||
|
|
||||||
def __initLayout(self):
|
def __initLayout(self):
|
||||||
self.setFixedHeight(70)
|
self.setFixedHeight(70)
|
||||||
@@ -72,26 +84,38 @@ class FolderSettingCard(SettingCard):
|
|||||||
self.hBoxLayout.addSpacing(16)
|
self.hBoxLayout.addSpacing(16)
|
||||||
|
|
||||||
def __connectSignalToSlot(self):
|
def __connectSignalToSlot(self):
|
||||||
self.lineEdit.textChanged.connect(self.pathValid)
|
self.lineEdit.textChanged.connect(self.validatePath)
|
||||||
|
if self.clearable:
|
||||||
|
signalBus.startSignal.connect(lambda: self.setDisabledClear(True))
|
||||||
|
signalBus.stopSignal.connect(lambda: self.setDisabledClear(False))
|
||||||
|
|
||||||
def pathValid(self, text):
|
@Slot(str)
|
||||||
|
def validatePath(self, text: str):
|
||||||
if not text:
|
if not text:
|
||||||
cfg.set(self.configItem, "")
|
cfg.set(self.configItem, "")
|
||||||
self.lineEdit.setValid(True)
|
self.lineEdit.setValid(True)
|
||||||
|
self.explorerAction.setDisabled(True)
|
||||||
|
self.setDisabledClear(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
path = Path(text)
|
path = Path(text)
|
||||||
if path.is_absolute() and path.is_dir() and path.exists():
|
if path.is_absolute() and path.is_dir() and path.exists():
|
||||||
cfg.set(self.configItem, text)
|
cfg.set(self.configItem, text)
|
||||||
self.lineEdit.setValid(True)
|
self.lineEdit.setValid(True)
|
||||||
|
self.setDisabledClear(False)
|
||||||
|
self.explorerAction.setDisabled(False)
|
||||||
else:
|
else:
|
||||||
self.lineEdit.setValid(False)
|
self.lineEdit.setValid(False)
|
||||||
|
self.setDisabledClear(True)
|
||||||
|
self.explorerAction.setDisabled(True)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
def openExplorer(self):
|
def openExplorer(self):
|
||||||
if self.configItem.value:
|
if self.configItem.value:
|
||||||
file_path = os.path.normpath(self.configItem.value)
|
path = os.path.realpath(self.configItem.value)
|
||||||
subprocess.Popen(f'explorer /select,"{file_path}"')
|
os.startfile(path)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
def browse(self):
|
def browse(self):
|
||||||
""" download folder card clicked slot """
|
""" download folder card clicked slot """
|
||||||
folder = QFileDialog.getExistingDirectory(
|
folder = QFileDialog.getExistingDirectory(
|
||||||
@@ -99,3 +123,58 @@ class FolderSettingCard(SettingCard):
|
|||||||
if not folder or cfg.get(self.configItem) == folder:
|
if not folder or cfg.get(self.configItem) == folder:
|
||||||
return
|
return
|
||||||
self.lineEdit.setText(folder)
|
self.lineEdit.setText(folder)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def showClearDialog(self):
|
||||||
|
path = Path(self.lineEdit.text())
|
||||||
|
if path in self.clearQueue:
|
||||||
|
notification.error(f"{self.titleGroup}'s directory is already being cleared!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not (path.is_absolute() and path.is_dir() and path.exists()):
|
||||||
|
notification.error(f"{self.titleGroup} directory path is not valid!")
|
||||||
|
return
|
||||||
|
|
||||||
|
w = ClearMessageBox("Delete all files", f"Are you sure you want to delete all files in {self.titleGroup}'s directory:\n\n{path}?")
|
||||||
|
w.exec()
|
||||||
|
yesPressed, deleteFolder = w.getResponse()
|
||||||
|
if yesPressed:
|
||||||
|
self.clear(path, deleteFolder)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def clear(self, path: Path, deleteFolder: bool):
|
||||||
|
signalBus.disableStartSignal.emit(True)
|
||||||
|
|
||||||
|
self.clearQueue.add(path)
|
||||||
|
self.lineEdit.setDisabled(True)
|
||||||
|
self.clearAction.setDisabled(True)
|
||||||
|
|
||||||
|
worker = ClearWorker(path, deleteFolder)
|
||||||
|
worker.finishSignal.connect(lambda successful: self.afterClear(path, deleteFolder, successful))
|
||||||
|
signalBus.threadPool.start(worker)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def afterClear(self, path: Path, deleteFolder: bool, successful: bool):
|
||||||
|
if successful:
|
||||||
|
if deleteFolder:
|
||||||
|
self.lineEdit.clear()
|
||||||
|
notification.success(f"Successfully deleted {self.titleGroup}'s directory")
|
||||||
|
else:
|
||||||
|
notification.success(f"Successfully cleared {self.titleGroup}'s directory")
|
||||||
|
else:
|
||||||
|
notification.error(f"Error clearing {self.titleGroup}'s directory")
|
||||||
|
|
||||||
|
self.lineEdit.setDisabled(False)
|
||||||
|
self.clearAction.setDisabled(False)
|
||||||
|
self.clearQueue.remove(path)
|
||||||
|
if not self.clearQueue:
|
||||||
|
signalBus.disableStartSignal.emit(False)
|
||||||
|
|
||||||
|
def setDisabledClear(self, value: bool):
|
||||||
|
if not self.clearable:
|
||||||
|
return
|
||||||
|
if not value and signalBus.scriptRunning():
|
||||||
|
value = True
|
||||||
|
|
||||||
|
self.clearAction.setDisabled(value)
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class NavigationActionButtons(QWidget):
|
|||||||
|
|
||||||
signalBus.startSignal.connect(lambda: self.startButton.setText("Stop"))
|
signalBus.startSignal.connect(lambda: self.startButton.setText("Stop"))
|
||||||
signalBus.stopSignal.connect(lambda: self.startButton.setText("Start"))
|
signalBus.stopSignal.connect(lambda: self.startButton.setText("Start"))
|
||||||
|
signalBus.disableStartSignal.connect(lambda state: self.startButton.setDisabled(state))
|
||||||
|
|
||||||
def onSelectAllClicked(self):
|
def onSelectAllClicked(self):
|
||||||
signalBus.selectAllClicked.emit()
|
signalBus.selectAllClicked.emit()
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ from qfluentwidgets import FluentIcon as FIF
|
|||||||
|
|
||||||
from .logger_interface import LoggerInterface
|
from .logger_interface import LoggerInterface
|
||||||
from .setting_interface import SettingInterface
|
from .setting_interface import SettingInterface
|
||||||
|
from ..common.notification import notification
|
||||||
from ..common.config import ZH_SUPPORT_URL, EN_SUPPORT_URL, cfg
|
from ..common.config import ZH_SUPPORT_URL, EN_SUPPORT_URL, cfg
|
||||||
from ..common.signal_bus import signalBus
|
from ..common.signal_bus import signalBus
|
||||||
from ..common import resource
|
from ..common import resource
|
||||||
|
from ..components.clear_messagebox import ClearMessageBox
|
||||||
from ..components.navigation_checkbox import NavigationCheckBox
|
from ..components.navigation_checkbox import NavigationCheckBox
|
||||||
from ..components.navigation_action_buttons import NavigationActionButtons
|
from ..components.navigation_action_buttons import NavigationActionButtons
|
||||||
from ..components.navigation_logo import NavigationLogoWidget
|
from ..components.navigation_logo import NavigationLogoWidget
|
||||||
@@ -68,6 +70,10 @@ class MainWindow(FluentWindow):
|
|||||||
self.addSubInterface(
|
self.addSubInterface(
|
||||||
self.settingInterface, FIF.SETTING, self.tr('Settings'), NavigationItemPosition.BOTTOM)
|
self.settingInterface, FIF.SETTING, self.tr('Settings'), NavigationItemPosition.BOTTOM)
|
||||||
|
|
||||||
|
notification.mainWindow = self
|
||||||
|
ClearMessageBox.mainWindow = self
|
||||||
|
|
||||||
|
|
||||||
def initWindow(self):
|
def initWindow(self):
|
||||||
self.resize(960, 780)
|
self.resize(960, 780)
|
||||||
self.setMinimumWidth(760)
|
self.setMinimumWidth(760)
|
||||||
|
|||||||
@@ -34,8 +34,10 @@ class SettingInterface(ScrollArea):
|
|||||||
self.gamePathCard = FolderSettingCard(
|
self.gamePathCard = FolderSettingCard(
|
||||||
cfg.gamePath,
|
cfg.gamePath,
|
||||||
FIF.GAME,
|
FIF.GAME,
|
||||||
|
'Core',
|
||||||
self.tr("Koikatsu directory"),
|
self.tr("Koikatsu directory"),
|
||||||
parent=self.coreGroup
|
parent=self.coreGroup,
|
||||||
|
clearable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
# createBackup
|
# createBackup
|
||||||
@@ -44,6 +46,7 @@ class SettingInterface(ScrollArea):
|
|||||||
self.backupPathCard = FolderSettingCard(
|
self.backupPathCard = FolderSettingCard(
|
||||||
cfg.backupPath,
|
cfg.backupPath,
|
||||||
FIF.ZIP_FOLDER,
|
FIF.ZIP_FOLDER,
|
||||||
|
'Create Backup',
|
||||||
self.tr("Backup directory"),
|
self.tr("Backup directory"),
|
||||||
parent=self.backupGroup
|
parent=self.backupGroup
|
||||||
)
|
)
|
||||||
@@ -82,7 +85,8 @@ class SettingInterface(ScrollArea):
|
|||||||
self.fckksPathCard = FolderSettingCard(
|
self.fckksPathCard = FolderSettingCard(
|
||||||
cfg.fccksPath,
|
cfg.fccksPath,
|
||||||
FIF.DOWNLOAD,
|
FIF.DOWNLOAD,
|
||||||
self.tr("Backup directory"),
|
'Filter & Convert',
|
||||||
|
self.tr("Input directory"),
|
||||||
parent=self.fckksGroup
|
parent=self.fckksGroup
|
||||||
)
|
)
|
||||||
self.convertCard = SwitchSettingCard(
|
self.convertCard = SwitchSettingCard(
|
||||||
@@ -99,6 +103,7 @@ class SettingInterface(ScrollArea):
|
|||||||
self.installPathCard = FolderSettingCard(
|
self.installPathCard = FolderSettingCard(
|
||||||
cfg.installPath,
|
cfg.installPath,
|
||||||
FIF.DOWNLOAD,
|
FIF.DOWNLOAD,
|
||||||
|
'Install Chara',
|
||||||
self.tr("Input directory"),
|
self.tr("Input directory"),
|
||||||
parent=self.installGroup
|
parent=self.installGroup
|
||||||
)
|
)
|
||||||
@@ -124,6 +129,7 @@ class SettingInterface(ScrollArea):
|
|||||||
self.removePathCard = FolderSettingCard(
|
self.removePathCard = FolderSettingCard(
|
||||||
cfg.removePath,
|
cfg.removePath,
|
||||||
FIF.DOWNLOAD,
|
FIF.DOWNLOAD,
|
||||||
|
'Remove Chara',
|
||||||
self.tr("Input directory"),
|
self.tr("Input directory"),
|
||||||
parent=self.removeGroup
|
parent=self.removeGroup
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ PySide6_Addons==6.7.2
|
|||||||
PySide6_Essentials==6.7.2
|
PySide6_Essentials==6.7.2
|
||||||
PySideSix-Frameless-Window==0.4.3
|
PySideSix-Frameless-Window==0.4.3
|
||||||
pywin32==306
|
pywin32==306
|
||||||
|
Send2Trash==1.8.3
|
||||||
setuptools==75.1.0
|
setuptools==75.1.0
|
||||||
shiboken6==6.7.2
|
shiboken6==6.7.2
|
||||||
wheel==0.44.0
|
wheel==0.44.0
|
||||||
|
|||||||
Reference in New Issue
Block a user