mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-08-01 21:59:36 +08:00
353 lines
12 KiB
Python
353 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Copyright (c) 2026 KenanZhu.
|
|
All rights reserved.
|
|
|
|
This software is provided "as is", without any warranty of any kind.
|
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
|
See the LICENSE file for details.
|
|
"""
|
|
from datetime import datetime, timedelta
|
|
from typing import Any
|
|
|
|
from PySide6.QtCore import (
|
|
Qt,
|
|
Signal,
|
|
Slot,
|
|
QTimer
|
|
)
|
|
from PySide6.QtGui import (
|
|
QFont
|
|
)
|
|
from PySide6.QtWidgets import (
|
|
QDialog,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QListWidgetItem,
|
|
QMessageBox,
|
|
QVBoxLayout,
|
|
QWidget
|
|
)
|
|
|
|
from gui.ALBulletinWorker import ALBulletinFetchWorker
|
|
from gui.ALStatusLabel import ALStatusLabel
|
|
from gui.resources.ui.Ui_ALBulletinDialog import Ui_ALBulletinDialog
|
|
from managers.bulletin.BulletinManager import instance as bulletinInstance
|
|
|
|
|
|
class ALBulletinItemWidget(QWidget):
|
|
"""
|
|
Single bulletin item widget for the bulletin list.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
parent=None,
|
|
bulletin: dict[str, Any] = None
|
|
):
|
|
|
|
super().__init__(parent)
|
|
self.__bulletin = bulletin.copy() if bulletin else {}
|
|
self.modifyUi()
|
|
|
|
def modifyUi(
|
|
self
|
|
):
|
|
|
|
self.ItemWidgetLayout = QVBoxLayout()
|
|
self.ItemWidgetLayout.setSpacing(10)
|
|
self.ItemWidgetLayout.setContentsMargins(10, 5, 10, 5)
|
|
self.BulletinTitleLayout = QHBoxLayout()
|
|
self.BulletinTitleLabel = QLabel(self.__bulletin.get("title", "无标题"))
|
|
title_font = QFont()
|
|
title_font.setBold(True)
|
|
self.BulletinTitleLabel.setFont(title_font)
|
|
self.BulletinTitleLayout.addWidget(self.BulletinTitleLabel)
|
|
if self.__bulletin.get("isNew", False):
|
|
self.NewIndicatorLabel = QLabel("新 !")
|
|
self.NewIndicatorLabel.setStyleSheet(
|
|
"color: #DC0000;"\
|
|
"font-size: 10px;"\
|
|
"font-weight: bold;"\
|
|
"font-style: italic;"
|
|
)
|
|
self.NewIndicatorLabel.setFixedSize(25, 25)
|
|
self.BulletinTitleLayout.addWidget(self.NewIndicatorLabel)
|
|
else:
|
|
self.NewIndicatorLabel = None
|
|
if self.__bulletin.get("isEdited", False):
|
|
self.BulletinIsEditedLabel = QLabel("(已编辑)")
|
|
self.BulletinIsEditedLabel.setStyleSheet(
|
|
"color: #FF9800;"\
|
|
"font-size: 10px;"
|
|
)
|
|
self.BulletinTitleLayout.addWidget(self.BulletinIsEditedLabel)
|
|
self.BulletinTitleLayout.addStretch()
|
|
self.ItemWidgetLayout.addLayout(self.BulletinTitleLayout)
|
|
self.BulletinInfoLayout = QHBoxLayout()
|
|
self.BulletinDateLabel = QLabel()
|
|
try:
|
|
raw_dt = self.__bulletin.get("dateTime", "")
|
|
date_time = datetime.fromisoformat(raw_dt) if raw_dt else datetime.now()
|
|
except (ValueError, TypeError):
|
|
date_time = datetime.now()
|
|
self.BulletinDateLabel.setText(date_time.strftime("%Y-%m-%d %H:%M:%S"))
|
|
self.BulletinDateLabel.setStyleSheet("color: #969696; font-size: 11px;")
|
|
self.BulletinInfoLayout.addWidget(self.BulletinDateLabel)
|
|
self.BulletinAuthorLabel = QLabel(self.__bulletin.get("author", "未知"))
|
|
self.BulletinAuthorLabel.setStyleSheet("color: #969696; font-size: 11px;")
|
|
self.BulletinInfoLayout.addWidget(self.BulletinAuthorLabel)
|
|
self.BulletinInfoLayout.addStretch()
|
|
self.ItemWidgetLayout.addLayout(self.BulletinInfoLayout)
|
|
self.setLayout(self.ItemWidgetLayout)
|
|
self.__bulletin = None
|
|
|
|
def markAsRead(
|
|
self
|
|
):
|
|
|
|
if self.NewIndicatorLabel:
|
|
self.NewIndicatorLabel.hide()
|
|
|
|
|
|
class ALBulletinDialog(QDialog, Ui_ALBulletinDialog):
|
|
"""
|
|
Bulletin viewer dialog.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
parent=None
|
|
):
|
|
super().__init__(parent)
|
|
self.__sync_timer: QTimer | None = None
|
|
self.__fetch_worker: ALBulletinFetchWorker | None = None
|
|
self.__bulletin_mgr = bulletinInstance()
|
|
|
|
self.setupUi(self)
|
|
self.modifyUi()
|
|
self.connectSignals()
|
|
self.setupTimer()
|
|
if self.shouldAutoSync():
|
|
self.syncBulletin()
|
|
|
|
def modifyUi(
|
|
self
|
|
):
|
|
|
|
self.ALSyncStatusLabel = ALStatusLabel(self)
|
|
self.ALSyncStatusLabel.setFixedSize(30, 30)
|
|
self.SyncLayout.replaceWidget(
|
|
self.SyncStatusPlaceholder, self.ALSyncStatusLabel
|
|
)
|
|
title_font = QFont()
|
|
title_font.setBold(True)
|
|
title_font.setPointSize(15)
|
|
self.BulletinTitleLabel.setFont(title_font)
|
|
self.BulletinDateLabel.setStyleSheet("color: #969696;")
|
|
self.BulletinAuthorLabel.setStyleSheet("color: #969696;")
|
|
self.BulletinIsEditedLabel.setStyleSheet("color: #FF9800;")
|
|
self.BulletinIsEditedLabel.hide()
|
|
last_time = self.__bulletin_mgr.lastSyncTime()
|
|
if last_time:
|
|
self.setLastSyncTime(last_time)
|
|
self.updateBulletinList(self.__bulletin_mgr.bulletins())
|
|
|
|
def shouldAutoSync(
|
|
self
|
|
) -> bool:
|
|
|
|
last = self.__bulletin_mgr.lastSyncTime()
|
|
if last is None:
|
|
return True
|
|
try:
|
|
last_dt = datetime.fromisoformat(last)
|
|
interval_min = max(self.__bulletin_mgr.syncInterval()//2, 1)
|
|
threshold = timedelta(minutes=interval_min)
|
|
return (datetime.now().astimezone() - last_dt) > threshold
|
|
except (ValueError, TypeError):
|
|
return True
|
|
|
|
def setLastSyncTime(
|
|
self,
|
|
iso_str: str
|
|
):
|
|
"""
|
|
Set last sync time to update the UI status.
|
|
"""
|
|
|
|
try:
|
|
dt = datetime.fromisoformat(iso_str)
|
|
self.LastSyncDateTimeEdit.setDateTime(dt)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
def connectSignals(
|
|
self
|
|
):
|
|
|
|
self.BulletinListWidget.itemClicked.connect(self.onBulletinListWidgetItemClicked)
|
|
self.SyncButton.clicked.connect(self.syncBulletin)
|
|
|
|
def setupTimer(
|
|
self
|
|
):
|
|
|
|
self.__sync_timer = QTimer(self)
|
|
self.__sync_timer.timeout.connect(self.syncBulletin)
|
|
self.__sync_timer.start(self.__bulletin_mgr.syncInterval()*60*1000)
|
|
|
|
def updateBulletinList(
|
|
self,
|
|
bulletins: list[dict]
|
|
):
|
|
|
|
self.BulletinListWidget.clear()
|
|
sorted_list = sorted(
|
|
bulletins, key=lambda x: x.get("dateTime", ""), reverse=True
|
|
)
|
|
for bulletin in sorted_list:
|
|
item = QListWidgetItem()
|
|
item.setData(Qt.UserRole, bulletin)
|
|
widget = ALBulletinItemWidget(self, bulletin)
|
|
item.setSizeHint(widget.sizeHint())
|
|
self.BulletinListWidget.addItem(item)
|
|
self.BulletinListWidget.setItemWidget(item, widget)
|
|
|
|
def showBulletin(
|
|
self,
|
|
bulletin: dict
|
|
):
|
|
|
|
self.BulletinTitleLabel.setText(bulletin.get("title", "无标题"))
|
|
try:
|
|
raw_dt = bulletin.get("dateTime", "")
|
|
date_time = datetime.fromisoformat(raw_dt) if raw_dt else datetime.now()
|
|
except (ValueError, TypeError):
|
|
date_time = datetime.now()
|
|
self.BulletinDateLabel.setText(date_time.strftime("%Y-%m-%d %H:%M:%S"))
|
|
self.BulletinAuthorLabel.setText(bulletin.get("author", "未知"))
|
|
if bulletin.get("isEdited", False):
|
|
self.BulletinIsEditedLabel.show()
|
|
else:
|
|
self.BulletinIsEditedLabel.hide()
|
|
self.BulletinContentTextBrowser.setHtml(
|
|
"<div style='font-size: 14px;'>{content}</div>".format(
|
|
content=bulletin.get("content", "无内容")
|
|
)
|
|
)
|
|
|
|
def clearBulletin(
|
|
self
|
|
):
|
|
|
|
self.BulletinTitleLabel.setText("")
|
|
self.BulletinDateLabel.setText("")
|
|
self.BulletinAuthorLabel.setText("")
|
|
self.BulletinContentTextBrowser.setText("")
|
|
self.BulletinIsEditedLabel.hide()
|
|
|
|
def syncBulletin(
|
|
self
|
|
):
|
|
|
|
if self.__fetch_worker is not None:
|
|
return
|
|
self.SyncButton.setEnabled(False)
|
|
self.SyncButton.setText("同步中...")
|
|
self.ALSyncStatusLabel.status = ALStatusLabel.Status.RUNNING
|
|
self.SyncStatusDetailLabel.setText("")
|
|
self.SyncStatusDetailLabel.setStyleSheet("")
|
|
params = self.__bulletin_mgr.getSyncDateTimeAndRange()
|
|
self.__fetch_worker = ALBulletinFetchWorker(
|
|
self, self.__bulletin_mgr.apiUrl(), params
|
|
)
|
|
self.__fetch_worker.fetchWorkerIsFinished.connect(self.onBulletinsFetched)
|
|
self.__fetch_worker.fetchWorkerFinishedWithError.connect(self.onBulletinsFetchError)
|
|
self.__fetch_worker.start()
|
|
|
|
@Slot(dict)
|
|
def onBulletinsFetched(
|
|
self,
|
|
data: dict
|
|
):
|
|
|
|
worker = self.sender()
|
|
if worker is not self.__fetch_worker:
|
|
return
|
|
worker.fetchWorkerIsFinished.disconnect(self.onBulletinsFetched)
|
|
worker.fetchWorkerFinishedWithError.disconnect(self.onBulletinsFetchError)
|
|
worker.wait(2000)
|
|
worker.deleteLater()
|
|
self.__fetch_worker = None
|
|
bulletins = data.get("bulletins", [])
|
|
delete_ids = data.get("delete_ids", [])
|
|
merged = self.__bulletin_mgr.updateAndMergeBulletins(bulletins, delete_ids)
|
|
self.__bulletin_mgr.setLastSyncTime(datetime.now().astimezone().isoformat())
|
|
self.SyncButton.setEnabled(True)
|
|
self.SyncButton.setText("同步")
|
|
self.ALSyncStatusLabel.status = ALStatusLabel.Status.SUCCESS
|
|
self.SyncStatusDetailLabel.setText("同步成功")
|
|
self.SyncStatusDetailLabel.setStyleSheet("color: green;")
|
|
QTimer.singleShot(3000, self, self.clearSyncStatus)
|
|
|
|
self.setLastSyncTime(self.__bulletin_mgr.lastSyncTime())
|
|
self.updateBulletinList(merged)
|
|
self.clearBulletin()
|
|
interval_ms = self.__bulletin_mgr.syncInterval()*60*1000
|
|
if self.__sync_timer:
|
|
self.__sync_timer.start(interval_ms)
|
|
|
|
@Slot(str)
|
|
def onBulletinsFetchError(
|
|
self,
|
|
error_message: str
|
|
):
|
|
|
|
worker = self.sender()
|
|
if worker is not self.__fetch_worker:
|
|
return
|
|
worker.fetchWorkerIsFinished.disconnect(self.onBulletinsFetched)
|
|
worker.fetchWorkerFinishedWithError.disconnect(self.onBulletinsFetchError)
|
|
worker.wait(2000)
|
|
worker.deleteLater()
|
|
self.__fetch_worker = None
|
|
self.SyncButton.setEnabled(True)
|
|
self.SyncButton.setText("重试")
|
|
self.ALSyncStatusLabel.status = ALStatusLabel.Status.FAILURE
|
|
self.SyncStatusDetailLabel.setText("同步失败,请检查网络连接")
|
|
self.SyncStatusDetailLabel.setStyleSheet("color: red;")
|
|
QMessageBox.warning(
|
|
self,
|
|
"警告 - AutoLibrary",
|
|
f"同步失败:{error_message}"
|
|
)
|
|
interval_ms = self.__bulletin_mgr.syncInterval()*60*1000
|
|
if self.__sync_timer:
|
|
self.__sync_timer.start(interval_ms)
|
|
|
|
def clearSyncStatus(
|
|
self
|
|
):
|
|
|
|
self.ALSyncStatusLabel.status = ALStatusLabel.Status.WAITING
|
|
self.SyncStatusDetailLabel.setText("")
|
|
self.SyncStatusDetailLabel.setStyleSheet("")
|
|
|
|
@Slot(QListWidgetItem)
|
|
def onBulletinListWidgetItemClicked(
|
|
self,
|
|
item: QListWidgetItem
|
|
):
|
|
|
|
if item is None or item.data(Qt.UserRole) is None:
|
|
return
|
|
bulletin = item.data(Qt.UserRole)
|
|
if bulletin.get("isNew", False):
|
|
self.__bulletin_mgr.markBulletinAsRead(bulletin["id"])
|
|
widget = self.BulletinListWidget.itemWidget(item)
|
|
if widget:
|
|
widget.markAsRead()
|
|
item.setData(Qt.UserRole, bulletin)
|
|
self.showBulletin(bulletin)
|