491 lines
18 KiB
Python
491 lines
18 KiB
Python
import sys
|
|
import os
|
|
import shutil
|
|
import UtilPack as util
|
|
import DataClass_Pupil as pupil
|
|
import MgrCalibreDB
|
|
import MgrPupilColDB
|
|
#import MgrCalibreLibs as calLib
|
|
|
|
|
|
from PyQt5.QtCore import Qt, QSettings, QRect
|
|
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton, QVBoxLayout, QLineEdit, \
|
|
QHBoxLayout, QTableWidget, QTableWidgetItem, QAbstractItemView, QHeaderView, QFileDialog, \
|
|
QListWidget, QListWidgetItem, QMessageBox
|
|
from PyQt5.QtGui import QResizeEvent, QCloseEvent, QColor
|
|
|
|
#QApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
|
|
|
|
class MyApp(QMainWindow):
|
|
m_DictDuplicate: dict[int, list[str]] = {}
|
|
m_ListIncompleteMangas: list[str] = []
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.initUI()
|
|
self.loadINI()
|
|
self.initDB()
|
|
|
|
#
|
|
def closeEvent(self, a0: 'QCloseEvent | None'):
|
|
filenameLog = f"{util.GetCurrentTime()}_DbgLog.txt"
|
|
pathDbgLog = os.path.join( self.pathLog, filenameLog)
|
|
util.SaveDbgMessages(pathDbgLog)
|
|
self.saveINI()
|
|
super().closeEvent(a0)
|
|
|
|
#
|
|
def resizeEvent(self, a0: 'QResizeEvent | None'):
|
|
PScreen = QApplication.primaryScreen()
|
|
rcDesktop: QRect = QRect(0, 0, 800, 600)
|
|
if PScreen is not None:
|
|
rcDesktop = PScreen.availableGeometry()
|
|
|
|
rcWnd = self.geometry()
|
|
if rcWnd.width() > rcDesktop.width():
|
|
rcWnd.setWidth(rcDesktop.width())
|
|
|
|
print(f"Resize Event: {rcWnd.width()}x{rcWnd.height()} at {rcWnd.x()},{rcWnd.y()}")
|
|
|
|
super().resizeEvent(a0)
|
|
|
|
#
|
|
def loadINI(self):
|
|
settings = QSettings('MyApp', 'settings')
|
|
|
|
self.pathLog = settings.value('LoggingPath', "~/Workspace/Log", type=str)
|
|
if False == os.path.exists(self.pathLog):
|
|
os.makedirs(self.pathLog, exist_ok=True)
|
|
|
|
self.pathDB = settings.value('PupilDBPath', "~/Workspace/DB", type=str)
|
|
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.edit_DB.setText(self.pathLastCalibre)
|
|
|
|
self.fileNamePupilDB = "MyMangaData.db"
|
|
self.fileNameCallibreDB = "metadata.db"
|
|
|
|
window_size = settings.value('window/size', '800, 600')
|
|
window_position = settings.value('window/position', '100, 100')
|
|
|
|
ItemCnt = settings.value('folders/count', 0, type=int)
|
|
for idx in range(ItemCnt):
|
|
item_text = settings.value(f'folders/{idx}', '', type=str)
|
|
if item_text:
|
|
self.listWidget_Folders.addItem(item_text)
|
|
|
|
#
|
|
def saveINI(self):
|
|
settings = QSettings('MyApp', 'settings')
|
|
|
|
# 키-값 형식으로 데이터 저장
|
|
settings.setValue('window/size', "1024,768")
|
|
settings.setValue('window/position', "100,100")
|
|
|
|
# 폴더 목록 저장
|
|
ListItems: list[QListWidgetItem] = self.listWidget_Folders.items(None)
|
|
for idx in range(len(ListItems)):
|
|
item = ListItems[idx]
|
|
settings.setValue(f'folders/{idx}', item.text())
|
|
|
|
settings.setValue('folders/count', self.listWidget_Folders.count())
|
|
settings.setValue('LastCalibrePath', self.edit_DB.text())
|
|
|
|
#
|
|
def initDB(self):
|
|
pathPupilDB = os.path.join(self.pathDB, self.fileNamePupilDB)
|
|
util.DbgOut(f"Loading Pupil DB from {pathPupilDB}", True)
|
|
self.DBPupil = MgrPupilColDB.MgrPupilColDB(pathPupilDB)
|
|
|
|
pathCalibreDB = os.path.join(self.pathLastCalibre, self.fileNameCallibreDB)
|
|
self.LoadCalibreDB(pathCalibreDB)
|
|
|
|
#
|
|
def MakeUI_Left(self):
|
|
layout = QVBoxLayout()
|
|
|
|
layout_top = QHBoxLayout()
|
|
self.listWidget_Folders = QListWidget()
|
|
self.listWidget_Folders.setFixedHeight(100)
|
|
|
|
layout_Btns = QVBoxLayout()
|
|
btn_FolderAdd = QPushButton("폴더 추가")
|
|
btn_FolderAdd.clicked.connect(self.on_btnFolderAdd_clicked)
|
|
btn_FolderDel = QPushButton("폴더 삭제")
|
|
btn_FolderDel.clicked.connect(self.on_btnFolderDel_clicked)
|
|
btn_FolderParss = QPushButton("폴더 파싱")
|
|
btn_FolderParss.clicked.connect(self.on_btnFolderParse_clicked)
|
|
|
|
layout_Btns.addWidget(btn_FolderAdd)
|
|
layout_Btns.addWidget(btn_FolderDel)
|
|
layout_Btns.addWidget(btn_FolderParss)
|
|
|
|
layout_top.addWidget(self.listWidget_Folders)
|
|
layout_top.addLayout(layout_Btns)
|
|
|
|
self.tableWidget_Src = QTableWidget()
|
|
self.tableWidget_Src.verticalHeader().setVisible(False)
|
|
self.tableWidget_Src.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
self.tableWidget_Src.setSortingEnabled(True)
|
|
self.tableWidget_Src.setColumnCount(5)
|
|
self.tableWidget_Src.setHorizontalHeaderLabels(["Path", "Title", "H.ID", "Img Cnt", "Dw Cnt"])
|
|
self.tableWidget_Src.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
self.tableWidget_Src.horizontalHeader().sectionClicked.connect(self.on_tableWidget_Src_headerClicked)
|
|
self.tableWidget_Src.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
self.tableWidget_Src.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
self.tableWidget_Src.itemSelectionChanged.connect(self.on_tableWidget_Src_itemSelectionChanged)
|
|
|
|
layout.addLayout(layout_top)
|
|
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
|
|
|
|
#
|
|
def MakeUI_Right(self):
|
|
layout_top = QHBoxLayout()
|
|
self.edit_DB = QLineEdit(self)
|
|
self.edit_DB.setReadOnly(True)
|
|
self.edit_DB.setText("...")
|
|
btn_DB = QPushButton("...")
|
|
btn_DB.setFixedWidth(50)
|
|
btn_DB.clicked.connect(self.on_btnDB_clicked)
|
|
layout_top.addWidget(self.edit_DB)
|
|
layout_top.addWidget(btn_DB)
|
|
|
|
self.tableWidget_DB = QTableWidget()
|
|
self.tableWidget_DB.verticalHeader().setVisible(False)
|
|
self.tableWidget_DB.setColumnCount(5)
|
|
self.tableWidget_DB.setHorizontalHeaderLabels(["Title", "Author", "Cover", "Exts", "ID"])
|
|
self.tableWidget_DB.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
self.tableWidget_DB.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
self.tableWidget_DB.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
self.tableWidget_DB.itemSelectionChanged.connect(self.on_tableWidget_DB_itemSelectionChanged)
|
|
|
|
layout = QVBoxLayout()
|
|
layout.addLayout(layout_top)
|
|
layout.addWidget(self.tableWidget_DB)
|
|
|
|
return layout
|
|
|
|
#
|
|
def MakeUI(self):
|
|
layout_L = self.MakeUI_Left()
|
|
layout_C = self.MakeUI_Center()
|
|
layout_R = self.MakeUI_Right()
|
|
|
|
# 레이아웃 설정
|
|
layout = QHBoxLayout()
|
|
layout.addLayout(layout_L, stretch = 10)
|
|
layout.addLayout(layout_C, stretch = 1)
|
|
layout.addLayout(layout_R, stretch = 10)
|
|
|
|
return layout
|
|
|
|
#
|
|
def initUI(self):
|
|
layout = self.MakeUI()
|
|
|
|
# 레이아웃을 윈도우에 적용
|
|
central_widget = QWidget()
|
|
central_widget.setLayout(layout)
|
|
self.setCentralWidget(central_widget)
|
|
self.setWindowTitle('Manga Database')
|
|
self.resize(800, 600)
|
|
self.show()
|
|
|
|
# 테이블의 특정 행에 배경색을 설정한다
|
|
# nRow: 배경색을 설정할 행 번호, color: 배경색 (Qt.GlobalColor)
|
|
def SrcTableRowBgColor(self, nRow:int, color:Qt.GlobalColor) -> None:
|
|
for col in range(self.tableWidget_Src.columnCount()):
|
|
item = self.tableWidget_Src.item(nRow, col)
|
|
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")
|
|
if False == os.path.exists(itemPathFull):
|
|
util.DbgOut(f"JSon File not found: {itemPathFull}", True)
|
|
return
|
|
|
|
data = pupil.PupilData(itemPathFull)
|
|
print(data.GetText())
|
|
|
|
|
|
## metadata.db
|
|
def LoadCalibreDB(self, path:str ) -> None:
|
|
pathDB = path
|
|
if True == os.path.isdir(path):
|
|
pathDB = os.path.join(path, "metadata.db")
|
|
print(f"LoadCalibreDB: {pathDB}")
|
|
|
|
if False == os.path.exists(pathDB):
|
|
util.DbgOut(f"Calibre DB not found: {pathDB}", True)
|
|
return
|
|
|
|
self.DBCalibre = MgrCalibreDB.MgrCalibreDB(pathDB)
|
|
listItems = self.DBCalibre.GetBookListforUI_ArcList()
|
|
for item in listItems:
|
|
strID = item[4]
|
|
strTitle = item[0]
|
|
strAuthor = item[1]
|
|
strHasCover = item[2]
|
|
strBookPath = item[3]
|
|
|
|
nRow = self.tableWidget_DB.rowCount()
|
|
self.tableWidget_DB.setRowCount(nRow + 1)
|
|
|
|
self.tableWidget_DB.setItem(nRow, 0, QTableWidgetItem(strTitle))
|
|
self.tableWidget_DB.setItem(nRow, 1, QTableWidgetItem(strAuthor))
|
|
|
|
itemCover = QTableWidgetItem(strHasCover)
|
|
if "0" == strHasCover:
|
|
itemCover.setForeground(QColor(255, 0, 0))
|
|
|
|
self.tableWidget_DB.setItem(nRow, 2, itemCover)
|
|
|
|
# format, uncompressed_size, name
|
|
tupleData = self.DBCalibre.GetDataByBookID(int(strID))
|
|
itemExt = QTableWidgetItem(tupleData[0])
|
|
pathArc = os.path.join(path, strBookPath, f"{tupleData[2]}.{tupleData[0]}")
|
|
if False == os.path.exists(pathArc):
|
|
itemExt.setForeground(QColor(255, 0, 0))
|
|
|
|
self.tableWidget_DB.setItem(nRow, 3, itemExt)
|
|
self.tableWidget_DB.setItem(nRow, 4, QTableWidgetItem(strID))
|
|
|
|
#
|
|
def on_btnFolderAdd_clicked(self):
|
|
folder_path = QFileDialog.getExistingDirectory(self, '폴더 선택', '')
|
|
|
|
if True == util.IsEmptyStr(folder_path):
|
|
return
|
|
|
|
pathAbs = os.path.abspath(folder_path)
|
|
if self.listWidget_Folders.findItems(pathAbs, Qt.MatchFlag.MatchExactly):
|
|
util.DbgOut(f"폴더가 이미 추가되어 있습니다: {pathAbs}", True)
|
|
return
|
|
|
|
self.listWidget_Folders.addItem(pathAbs)
|
|
|
|
#
|
|
def on_btnFolderDel_clicked(self):
|
|
selected_items = self.listWidget_Folders.selectedItems()
|
|
for item in selected_items:
|
|
row = self.listWidget_Folders.row(item)
|
|
self.listWidget_Folders.takeItem(row)
|
|
|
|
#
|
|
def on_btnFolderParse_clicked(self):
|
|
itemCount = self.listWidget_Folders.count()
|
|
if 0 == itemCount:
|
|
util.DbgOut("폴더가 선택되지 않았습니다.", True)
|
|
return
|
|
|
|
# 중복 검사을 위한 딕셔너리 초기화
|
|
self.m_DictDuplicate.clear()
|
|
# 다운로드가 완료되지 않은 만화 목록 초기화
|
|
self.m_ListIncompleteMangas.clear()
|
|
# 테이블 위젯 비우기
|
|
self.tableWidget_Src.setRowCount(0)
|
|
|
|
for idx in range(itemCount):
|
|
item = self.listWidget_Folders.item(idx)
|
|
if item is None:
|
|
continue
|
|
|
|
self.LoadSrcFolder(item.text())
|
|
|
|
#
|
|
def on_btnDB_clicked(self):
|
|
folder_path = QFileDialog.getExistingDirectory(self, '폴더 선택', '')
|
|
|
|
if True == util.IsEmptyStr(folder_path):
|
|
return
|
|
|
|
if self.edit_DB.text() == folder_path:
|
|
return
|
|
|
|
self.edit_DB.setText(folder_path)
|
|
self.LoadCalibreDB(folder_path)
|
|
|
|
#
|
|
def on_tableWidget_Src_headerClicked(self, logicalIndex: int):
|
|
# 정렬 상태 확인 및 변경
|
|
HorHeader = self.tableWidget_Src.horizontalHeader()
|
|
if HorHeader is None:
|
|
return
|
|
|
|
if HorHeader.sortIndicatorOrder() == Qt.SortOrder.AscendingOrder:
|
|
sort_order = Qt.SortOrder.DescendingOrder
|
|
else:
|
|
sort_order = Qt.SortOrder.AscendingOrder
|
|
|
|
HorHeader.setSortIndicator(logicalIndex, sort_order)
|
|
self.tableWidget_Src.sortItems(logicalIndex, HorHeader.sortIndicatorOrder() )
|
|
|
|
#
|
|
def on_tableWidget_Src_itemSelectionChanged(self):
|
|
pass
|
|
|
|
#
|
|
def on_tableWidget_DB_itemSelectionChanged(self):
|
|
pass
|
|
|
|
#
|
|
def on_btn_Emptyfolder_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)
|
|
return
|
|
|
|
for nID, paths in self.m_DictDuplicate.items():
|
|
if len(paths) <= 1:
|
|
continue
|
|
|
|
# 첫번째 만화의 해시를 검사한다.
|
|
# -해시가 멀쩡하면 정상으로 판단, 2번째부터는 그냥 지운다.
|
|
# 만약 해시가 이상하면 해시가 멀쩡한 놈을 찾는다.
|
|
# -멀쩡한 놈을 찾으면 그걸 보존, 다른 나머지를 지운다.
|
|
# 해시가 전부 이상하면 일단 전부 딴데로 백업.
|
|
# - 해당하는 파일을 다운받거나, 중복되는 것 중에서 조합해서 복원. 그건 다른 데서 하자.
|
|
|
|
|
|
#
|
|
def on_btn_Archive_clicked(self):
|
|
itemCount = self.tableWidget_Src.rowCount()
|
|
if 0 >= itemCount:
|
|
return
|
|
|
|
for idx in range(itemCount):
|
|
item = self.tableWidget_Src.item(idx, 0)
|
|
self.LoadPupilJson(item.text())
|
|
|
|
#
|
|
def on_btn_EnterCalibre_clicked(self):
|
|
pass
|
|
|
|
"""
|
|
if __name__ == '__main__':
|
|
app = QApplication(sys.argv)
|
|
|
|
app.setQuitOnLastWindowClosed(True)
|
|
main_window = MyApp()
|
|
#main_window.show()
|
|
|
|
sys.exit(app.exec_())
|
|
"""
|
|
|