오래 걸리는 작업이 쓰레드로 돌아가게 수정, UI 다운 안되도록
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ Temp/
|
||||
__pycache__/
|
||||
.venv/
|
||||
.git/
|
||||
download_sample.json
|
||||
|
||||
398
MgrCalibreUI.py
398
MgrCalibreUI.py
@@ -1,5 +1,6 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
import UtilPack as util
|
||||
import DataClass_Pupil as pupil
|
||||
@@ -8,7 +9,7 @@ import MgrPupilColDB
|
||||
#import MgrCalibreLibs as calLib
|
||||
|
||||
|
||||
from PyQt5.QtCore import Qt, QSettings, QRect
|
||||
from PyQt5.QtCore import Qt, QSettings, QRect, QThread, QObject, pyqtSignal
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton, QVBoxLayout, QLineEdit, \
|
||||
QHBoxLayout, QTableWidget, QTableWidgetItem, QAbstractItemView, QHeaderView, QFileDialog, \
|
||||
QListWidget, QListWidgetItem, QMessageBox
|
||||
@@ -16,6 +17,104 @@ from PyQt5.QtGui import QResizeEvent, QCloseEvent, QColor
|
||||
|
||||
#QApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
|
||||
|
||||
class FolderParseWorker(QObject):
|
||||
itemParsed = pyqtSignal(str, str, str, int, int, int)
|
||||
finished = pyqtSignal()
|
||||
|
||||
def __init__(self, folderPaths: list[str]):
|
||||
super().__init__()
|
||||
self.folderPaths = folderPaths
|
||||
|
||||
def run(self):
|
||||
for path in self.folderPaths:
|
||||
listFiles = util.ListChildDirectories(path)
|
||||
for item in listFiles:
|
||||
pathDir = os.path.join(path, item)
|
||||
if not os.path.isdir(pathDir):
|
||||
continue
|
||||
|
||||
FullPath = os.path.join(pathDir, ".metadata")
|
||||
data = pupil.PupilData(FullPath)
|
||||
|
||||
title = data.GetTitle()
|
||||
strID = data.GetHitomiID()
|
||||
if util.IsEmptyStr(strID):
|
||||
strID = util.GetTextInBrakets(item)[0]
|
||||
|
||||
nImgListLen = data.GetImgFileCount()
|
||||
nImgFileCnt = len(util.ListContainFiles(pathDir))
|
||||
nID = int(strID)
|
||||
|
||||
self.itemParsed.emit(pathDir, title, strID, nImgListLen, nImgFileCnt, nID)
|
||||
del data
|
||||
|
||||
self.finished.emit()
|
||||
|
||||
|
||||
class ValidateWorker(QObject):
|
||||
itemMoved = pyqtSignal(str, str)
|
||||
error = pyqtSignal(str)
|
||||
finished = pyqtSignal()
|
||||
|
||||
def __init__(self, folderPaths: list[str], destPath: str):
|
||||
super().__init__()
|
||||
self.folderPaths = folderPaths
|
||||
self.destPath = destPath
|
||||
|
||||
def run(self):
|
||||
for pathItem in self.folderPaths:
|
||||
if not os.path.exists(pathItem):
|
||||
continue
|
||||
|
||||
try:
|
||||
baseName = os.path.basename(pathItem)
|
||||
pathDest = os.path.join(self.destPath, baseName)
|
||||
shutil.move(pathItem, pathDest)
|
||||
|
||||
if os.path.exists(pathDest):
|
||||
self.itemMoved.emit(pathItem, pathDest)
|
||||
|
||||
except Exception as e:
|
||||
strMsg = f"폴더 이동 중 오류 발생: {e}"
|
||||
self.error.emit(strMsg)
|
||||
|
||||
self.finished.emit()
|
||||
|
||||
|
||||
class ArchiveWorker(QObject):
|
||||
itemArchived = pyqtSignal(str, str)
|
||||
error = pyqtSignal(str)
|
||||
finished = pyqtSignal()
|
||||
|
||||
def __init__(self, folderPaths: list[str], destPath: str):
|
||||
super().__init__()
|
||||
self.folderPaths = folderPaths
|
||||
self.destPath = destPath
|
||||
|
||||
def run(self):
|
||||
for src_folder in self.folderPaths:
|
||||
baseName = os.path.basename(src_folder)
|
||||
parent_dir = os.path.dirname(src_folder)
|
||||
zip_path = os.path.join(self.destPath, f"{baseName}.zip")
|
||||
|
||||
try:
|
||||
files = []
|
||||
for root, dirs, files_in_dir in os.walk(src_folder):
|
||||
for file in files_in_dir:
|
||||
file_path = os.path.join(root, file)
|
||||
files.append(file_path)
|
||||
|
||||
bSuccess = util.CreateZIP(zip_path, files)
|
||||
if bSuccess:
|
||||
self.itemArchived.emit(src_folder, zip_path)
|
||||
else:
|
||||
self.error.emit(f"압축 실패 ({baseName})")
|
||||
except Exception as e:
|
||||
self.error.emit(f"압축 중 오류 발생 ({baseName}): {e}")
|
||||
|
||||
self.finished.emit()
|
||||
|
||||
|
||||
class MyApp(QMainWindow):
|
||||
m_DictDuplicate: dict[int, list[str]] = {}
|
||||
m_ListIncompleteMangas: list[str] = []
|
||||
@@ -54,16 +153,19 @@ class MyApp(QMainWindow):
|
||||
settings = QSettings('MyApp', 'settings')
|
||||
|
||||
self.pathLog = settings.value('LoggingPath', "~/Workspace/Log", type=str)
|
||||
self.pathLog = os.path.expanduser(self.pathLog)
|
||||
if False == os.path.exists(self.pathLog):
|
||||
os.makedirs(self.pathLog, exist_ok=True)
|
||||
|
||||
self.pathDB = settings.value('PupilDBPath', "~/Workspace/DB", type=str)
|
||||
self.pathDB = os.path.expanduser(self.pathDB)
|
||||
if False == os.path.exists(self.pathDB):
|
||||
os.makedirs(self.pathDB, exist_ok=True)
|
||||
|
||||
self.pathLastCalibre = settings.value('LastCalibrePath', "~/Calibre Library", type=str)
|
||||
if False == os.path.exists(self.pathDB):
|
||||
os.makedirs(self.pathDB, exist_ok=True)
|
||||
self.pathLastCalibre = os.path.expanduser(self.pathLastCalibre)
|
||||
if False == os.path.exists(self.pathLastCalibre):
|
||||
os.makedirs(self.pathLastCalibre, exist_ok=True)
|
||||
|
||||
self.edit_DB.setText(self.pathLastCalibre)
|
||||
|
||||
@@ -128,6 +230,21 @@ class MyApp(QMainWindow):
|
||||
layout_top.addWidget(self.listWidget_Folders)
|
||||
layout_top.addLayout(layout_Btns)
|
||||
|
||||
layout_mid = QVBoxLayout()
|
||||
btn_ChkDuplicate = QPushButton("유효성 검사")
|
||||
btn_ChkDuplicate.clicked.connect(self.on_btn_Validate_clicked)
|
||||
btn_Archive = QPushButton("압축 및 데이터 저장")
|
||||
btn_Archive.clicked.connect(self.on_btn_Archive_clicked)
|
||||
btn_EnterCalibre = QPushButton("컬리버에 삽입")
|
||||
btn_EnterCalibre.clicked.connect(self.on_btn_EnterCalibre_clicked)
|
||||
btn_MakeDownList = QPushButton("다운로드 목록 생성")
|
||||
btn_MakeDownList.clicked.connect(self.on_btn_MakeDownList_clicked)
|
||||
|
||||
layout_mid.addWidget(btn_ChkDuplicate)
|
||||
layout_mid.addWidget(btn_Archive)
|
||||
layout_mid.addWidget(btn_EnterCalibre)
|
||||
layout_mid.addWidget(btn_MakeDownList)
|
||||
|
||||
self.tableWidget_Src = QTableWidget()
|
||||
self.tableWidget_Src.verticalHeader().setVisible(False)
|
||||
self.tableWidget_Src.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
@@ -141,29 +258,14 @@ class MyApp(QMainWindow):
|
||||
self.tableWidget_Src.itemSelectionChanged.connect(self.on_tableWidget_Src_itemSelectionChanged)
|
||||
|
||||
layout.addLayout(layout_top)
|
||||
layout.addLayout(layout_mid)
|
||||
layout.addWidget(self.tableWidget_Src)
|
||||
|
||||
return layout
|
||||
|
||||
#
|
||||
def MakeUI_Center(self):
|
||||
layout = QVBoxLayout()
|
||||
|
||||
btn_Emptyfolder = QPushButton("빈 폴더 이동")
|
||||
btn_Emptyfolder.clicked.connect(self.on_btn_Emptyfolder_clicked)
|
||||
btn_ChkDuplicate = QPushButton("중복 검사 및 제거")
|
||||
btn_ChkDuplicate.clicked.connect(self.on_btn_ChkDuplicate_clicked)
|
||||
btn_Archive = QPushButton("압축 및 데이터 저장")
|
||||
btn_Archive.clicked.connect(self.on_btn_Archive_clicked)
|
||||
btn_EnterCalibre = QPushButton("컬리버에 삽입")
|
||||
btn_EnterCalibre.clicked.connect(self.on_btn_EnterCalibre_clicked)
|
||||
|
||||
layout.addWidget(btn_Emptyfolder)
|
||||
layout.addWidget(btn_ChkDuplicate)
|
||||
layout.addWidget(btn_Archive)
|
||||
layout.addWidget(btn_EnterCalibre)
|
||||
|
||||
return layout
|
||||
pass
|
||||
|
||||
#
|
||||
def MakeUI_Right(self):
|
||||
@@ -226,71 +328,6 @@ class MyApp(QMainWindow):
|
||||
if item:
|
||||
item.setBackground(Qt.GlobalColor(color))
|
||||
|
||||
#
|
||||
def LoadSrcFolder(self, path:str)-> None:
|
||||
# 폴더가 없으면 리턴
|
||||
if True == util.IsEmptyStr(path) or False == os.path.exists(path):
|
||||
return
|
||||
|
||||
# 폴더의 내용물을 읽어온다.
|
||||
listFiles = util.ListChildDirectories(path)
|
||||
util.DbgOut(f"Src Count : {len(listFiles)}", True)
|
||||
|
||||
# 테이블 초기화
|
||||
nLastRow = self.tableWidget_Src.rowCount()
|
||||
self.tableWidget_Src.setRowCount(nLastRow + len(listFiles))
|
||||
|
||||
nRow = nLastRow
|
||||
# 테이블에 데이터 추가
|
||||
for item in listFiles:
|
||||
pathDir = os.path.join(path, item)
|
||||
if False == os.path.isdir(pathDir):
|
||||
util.DbgOut(f"Not a directory: {pathDir}", True)
|
||||
continue
|
||||
|
||||
item_0 = QTableWidgetItem(pathDir)
|
||||
self.tableWidget_Src.setItem(nRow, 0, item_0)
|
||||
|
||||
FullPath = os.path.join(pathDir, ".metadata")
|
||||
data = pupil.PupilData(FullPath)
|
||||
util.DbgOut(f"Loaded MetaData Path : {FullPath}", True)
|
||||
|
||||
item_1 = QTableWidgetItem(data.GetTitle())
|
||||
self.tableWidget_Src.setItem(nRow, 1, item_1)
|
||||
|
||||
strID = data.GetHitomiID()
|
||||
if True == util.IsEmptyStr(strID):
|
||||
strID = util.GetTextInBrakets(item)[0]
|
||||
|
||||
item_2 = QTableWidgetItem(strID)
|
||||
self.tableWidget_Src.setItem(nRow, 2, item_2)
|
||||
|
||||
nImgListLen = data.GetImgFileCount()
|
||||
item_3 = QTableWidgetItem(f"{nImgListLen}")
|
||||
self.tableWidget_Src.setItem(nRow, 3, item_3)
|
||||
|
||||
nImgFileCnt = len(util.ListContainFiles(pathDir))
|
||||
item_4 = QTableWidgetItem(f"{nImgFileCnt}")
|
||||
self.tableWidget_Src.setItem(nRow, 4, item_4)
|
||||
|
||||
# 중복 검사 를 위해 경로를 딕셔너리에 저장
|
||||
nID = int(strID)
|
||||
if nID not in self.m_DictDuplicate:
|
||||
self.m_DictDuplicate[nID] = []
|
||||
self.m_DictDuplicate[nID].append(pathDir)
|
||||
|
||||
# 중복된 만화가 있으면 배경색을 변경
|
||||
if len(self.m_DictDuplicate[nID]) > 1 :
|
||||
self.SrcTableRowBgColor(nRow, Qt.GlobalColor.green)
|
||||
|
||||
# JSon 데이터의 파일 개수와 실제 다운받은 파일 개수가 다르면 리스트에 저장, 표시한다.
|
||||
if 0 >= nImgFileCnt or 0 >= nImgListLen or nImgFileCnt != nImgListLen:
|
||||
self.m_ListIncompleteMangas.append(pathDir)
|
||||
self.SrcTableRowBgColor(nRow, Qt.GlobalColor.lightGray)
|
||||
|
||||
nRow += 1
|
||||
del data
|
||||
|
||||
#
|
||||
def LoadPupilJson(self, path:str) -> None:
|
||||
itemPathFull = os.path.join(path, ".metadata")
|
||||
@@ -379,12 +416,53 @@ class MyApp(QMainWindow):
|
||||
# 테이블 위젯 비우기
|
||||
self.tableWidget_Src.setRowCount(0)
|
||||
|
||||
folderPaths = []
|
||||
for idx in range(itemCount):
|
||||
item = self.listWidget_Folders.item(idx)
|
||||
if item is None:
|
||||
continue
|
||||
folderPaths.append(item.text())
|
||||
|
||||
self.LoadSrcFolder(item.text())
|
||||
btn = self.sender()
|
||||
if btn is not None:
|
||||
btn.setEnabled(False)
|
||||
|
||||
self._thread = QThread()
|
||||
self._worker = FolderParseWorker(folderPaths)
|
||||
self._worker.moveToThread(self._thread)
|
||||
|
||||
self._thread.started.connect(self._worker.run)
|
||||
self._worker.itemParsed.connect(self._onItemParsed)
|
||||
self._worker.finished.connect(self._thread.quit)
|
||||
self._worker.finished.connect(self._worker.deleteLater)
|
||||
self._thread.finished.connect(self._thread.deleteLater)
|
||||
if btn is not None:
|
||||
self._thread.finished.connect(lambda: btn.setEnabled(True))
|
||||
|
||||
self._thread.start()
|
||||
|
||||
#
|
||||
def _onItemParsed(self, pathDir: str, title: str, strID: str,
|
||||
nImgListLen: int, nImgFileCnt: int, nID: int):
|
||||
nRow = self.tableWidget_Src.rowCount()
|
||||
self.tableWidget_Src.setRowCount(nRow + 1)
|
||||
|
||||
self.tableWidget_Src.setItem(nRow, 0, QTableWidgetItem(pathDir))
|
||||
self.tableWidget_Src.setItem(nRow, 1, QTableWidgetItem(title))
|
||||
self.tableWidget_Src.setItem(nRow, 2, QTableWidgetItem(strID))
|
||||
self.tableWidget_Src.setItem(nRow, 3, QTableWidgetItem(f"{nImgListLen}"))
|
||||
self.tableWidget_Src.setItem(nRow, 4, QTableWidgetItem(f"{nImgFileCnt}"))
|
||||
|
||||
if nID not in self.m_DictDuplicate:
|
||||
self.m_DictDuplicate[nID] = []
|
||||
self.m_DictDuplicate[nID].append(pathDir)
|
||||
|
||||
if len(self.m_DictDuplicate[nID]) > 1:
|
||||
self.SrcTableRowBgColor(nRow, Qt.GlobalColor.green)
|
||||
|
||||
if nImgFileCnt <= 0 or nImgListLen <= 0 or nImgFileCnt != nImgListLen:
|
||||
self.m_ListIncompleteMangas.append(pathDir)
|
||||
self.SrcTableRowBgColor(nRow, Qt.GlobalColor.lightGray)
|
||||
|
||||
#
|
||||
def on_btnDB_clicked(self):
|
||||
@@ -423,61 +501,135 @@ class MyApp(QMainWindow):
|
||||
pass
|
||||
|
||||
#
|
||||
def on_btn_Emptyfolder_clicked(self):
|
||||
def on_btn_Validate_clicked(self):
|
||||
folder_path = QFileDialog.getExistingDirectory(self, '폴더 선택', '')
|
||||
|
||||
for pathItem in self.m_ListIncompleteMangas:
|
||||
# 유효하지 않은 경로면 건드리지 말자
|
||||
if False == os.path.exists(pathItem):
|
||||
continue
|
||||
|
||||
# 폴더 이동
|
||||
try:
|
||||
baseName = os.path.basename(pathItem)
|
||||
pathDest = os.path.join(folder_path, baseName)
|
||||
shutil.move(pathItem, pathDest)
|
||||
|
||||
if True == os.path.exists(pathDest):
|
||||
util.DbgOut(f"폴더 이동 완료: {pathItem} -> {pathDest}")
|
||||
|
||||
except Exception as e:
|
||||
strMsg = f"폴더 이동 중 오류 발생: {e}"
|
||||
util.DbgOut(strMsg, True)
|
||||
QMessageBox.critical(self, "Error", strMsg)
|
||||
|
||||
#
|
||||
def on_btn_ChkDuplicate_clicked(self):
|
||||
if len(self.m_DictDuplicate) == 0:
|
||||
util.DbgOut("중복 검사할 데이터가 없습니다.", True)
|
||||
if util.IsEmptyStr(folder_path):
|
||||
return
|
||||
|
||||
for nID, paths in self.m_DictDuplicate.items():
|
||||
if len(paths) <= 1:
|
||||
continue
|
||||
btn = self.sender()
|
||||
if btn is not None:
|
||||
btn.setEnabled(False)
|
||||
|
||||
# 첫번째 만화의 해시를 검사한다.
|
||||
# -해시가 멀쩡하면 정상으로 판단, 2번째부터는 그냥 지운다.
|
||||
# 만약 해시가 이상하면 해시가 멀쩡한 놈을 찾는다.
|
||||
# -멀쩡한 놈을 찾으면 그걸 보존, 다른 나머지를 지운다.
|
||||
# 해시가 전부 이상하면 일단 전부 딴데로 백업.
|
||||
# - 해당하는 파일을 다운받거나, 중복되는 것 중에서 조합해서 복원. 그건 다른 데서 하자.
|
||||
itemsToMove = list(self.m_ListIncompleteMangas)
|
||||
|
||||
self._threadValidate = QThread()
|
||||
self._workerValidate = ValidateWorker(itemsToMove, folder_path)
|
||||
self._workerValidate.moveToThread(self._threadValidate)
|
||||
|
||||
self._threadValidate.started.connect(self._workerValidate.run)
|
||||
self._workerValidate.itemMoved.connect(self._onValidateItemMoved)
|
||||
self._workerValidate.error.connect(self._onValidateError)
|
||||
self._workerValidate.finished.connect(self._threadValidate.quit)
|
||||
self._workerValidate.finished.connect(self._workerValidate.deleteLater)
|
||||
self._threadValidate.finished.connect(self._threadValidate.deleteLater)
|
||||
if btn is not None:
|
||||
self._threadValidate.finished.connect(lambda: btn.setEnabled(True))
|
||||
|
||||
self._threadValidate.start()
|
||||
|
||||
#
|
||||
def _onValidateItemMoved(self, pathItem: str, pathDest: str):
|
||||
util.DbgOut(f"폴더 이동 완료: {pathItem} -> {pathDest}")
|
||||
|
||||
#
|
||||
def _onValidateError(self, strMsg: str):
|
||||
util.DbgOut(strMsg, True)
|
||||
QMessageBox.critical(self, "Error", strMsg)
|
||||
|
||||
|
||||
#
|
||||
def on_btn_Archive_clicked(self):
|
||||
itemCount = self.tableWidget_Src.rowCount()
|
||||
if 0 >= itemCount:
|
||||
if itemCount <= 0:
|
||||
return
|
||||
|
||||
folder_path = QFileDialog.getExistingDirectory(self, '압축 파일 저장 폴더 선택', '')
|
||||
if util.IsEmptyStr(folder_path):
|
||||
return
|
||||
|
||||
btn = self.sender()
|
||||
if btn is not None:
|
||||
btn.setEnabled(False)
|
||||
|
||||
folderPaths = []
|
||||
for idx in range(itemCount):
|
||||
item = self.tableWidget_Src.item(idx, 0)
|
||||
self.LoadPupilJson(item.text())
|
||||
if item is not None:
|
||||
folderPaths.append(item.text())
|
||||
|
||||
self._threadArchive = QThread()
|
||||
self._workerArchive = ArchiveWorker(folderPaths, folder_path)
|
||||
self._workerArchive.moveToThread(self._threadArchive)
|
||||
|
||||
self._threadArchive.started.connect(self._workerArchive.run)
|
||||
self._workerArchive.itemArchived.connect(self._onArchiveItemArchived)
|
||||
self._workerArchive.error.connect(self._onArchiveError)
|
||||
self._workerArchive.finished.connect(self._threadArchive.quit)
|
||||
self._workerArchive.finished.connect(self._workerArchive.deleteLater)
|
||||
self._threadArchive.finished.connect(self._threadArchive.deleteLater)
|
||||
if btn is not None:
|
||||
self._threadArchive.finished.connect(lambda: btn.setEnabled(True))
|
||||
|
||||
self._threadArchive.start()
|
||||
|
||||
#
|
||||
def _onArchiveItemArchived(self, src_folder: str, zip_path: str):
|
||||
util.DbgOut(f"압축 완료: {src_folder} -> {zip_path}")
|
||||
|
||||
#
|
||||
def _onArchiveError(self, strMsg: str):
|
||||
util.DbgOut(strMsg, True)
|
||||
QMessageBox.critical(self, "Error", strMsg)
|
||||
|
||||
|
||||
#
|
||||
def on_btn_EnterCalibre_clicked(self):
|
||||
pass
|
||||
|
||||
"""
|
||||
#
|
||||
def on_btn_MakeDownList_clicked(self):
|
||||
folder_path = QFileDialog.getExistingDirectory(self, '폴더 선택', '')
|
||||
if util.IsEmptyStr(folder_path):
|
||||
return
|
||||
|
||||
result = {}
|
||||
listDirs = util.ListChildDirectories(folder_path)
|
||||
for item in listDirs:
|
||||
pathDir = os.path.join(folder_path, item)
|
||||
if not os.path.isdir(pathDir):
|
||||
continue
|
||||
|
||||
metadataPath = os.path.join(pathDir, ".metadata")
|
||||
if not os.path.exists(metadataPath):
|
||||
continue
|
||||
|
||||
data = pupil.PupilData(metadataPath)
|
||||
if data.m_data is None:
|
||||
continue
|
||||
|
||||
title = data.GetTitle()
|
||||
strID = data.GetHitomiID()
|
||||
if util.IsEmptyStr(strID):
|
||||
strID = util.GetTextInBrakets(item)[0]
|
||||
|
||||
nImgListLen = data.GetImgFileCount()
|
||||
nImgFileCnt = len(util.ListContainFiles(pathDir))
|
||||
|
||||
if nImgFileCnt <= 0 or nImgListLen <= 0 or nImgFileCnt != nImgListLen:
|
||||
if not util.IsEmptyStr(strID):
|
||||
result[strID] = title
|
||||
|
||||
if result:
|
||||
timestamp = util.GetCurrentTime()
|
||||
filename = f"download_{timestamp}.json"
|
||||
savePath = os.path.join(folder_path, filename)
|
||||
with open(savePath, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
util.DbgOut(f"다운로드 목록이 생성되었습니다: {savePath}", True)
|
||||
else:
|
||||
util.DbgOut("완료되지 않은 다운로드가 없습니다.", True)
|
||||
|
||||
"""
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user