mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-08-02 22:19:37 +08:00
fix(bulletin): 修复公告栏系统多项逻辑缺陷与线程安全隐患
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+27
-12
@@ -130,7 +130,11 @@ class ALBulletinItemWidget(QWidget):
|
|||||||
self.ItemWidgetLayout.addLayout(self.BulletinTitleLayout)
|
self.ItemWidgetLayout.addLayout(self.BulletinTitleLayout)
|
||||||
self.BulletinInfoLayout = QHBoxLayout()
|
self.BulletinInfoLayout = QHBoxLayout()
|
||||||
self.BulletinDateLabel = QLabel()
|
self.BulletinDateLabel = QLabel()
|
||||||
date_time = datetime.fromisoformat(self.__bulletin.get("dateTime", ""))
|
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.setText(date_time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
self.BulletinDateLabel.setStyleSheet("color: #969696; font-size: 11px;")
|
self.BulletinDateLabel.setStyleSheet("color: #969696; font-size: 11px;")
|
||||||
self.BulletinInfoLayout.addWidget(self.BulletinDateLabel)
|
self.BulletinInfoLayout.addWidget(self.BulletinDateLabel)
|
||||||
@@ -258,7 +262,11 @@ class ALBulletinDialog(QDialog, Ui_ALBulletinDialog):
|
|||||||
):
|
):
|
||||||
|
|
||||||
self.BulletinTitleLabel.setText(bulletin.get("title", "无标题"))
|
self.BulletinTitleLabel.setText(bulletin.get("title", "无标题"))
|
||||||
date_time = datetime.fromisoformat(bulletin.get("dateTime", ""))
|
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.BulletinDateLabel.setText(date_time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
self.BulletinAuthorLabel.setText(bulletin.get("author", "未知"))
|
self.BulletinAuthorLabel.setText(bulletin.get("author", "未知"))
|
||||||
if bulletin.get("isEdited", False):
|
if bulletin.get("isEdited", False):
|
||||||
@@ -306,12 +314,15 @@ class ALBulletinDialog(QDialog, Ui_ALBulletinDialog):
|
|||||||
data: dict
|
data: dict
|
||||||
):
|
):
|
||||||
|
|
||||||
if self.__fetch_worker:
|
worker = self.sender()
|
||||||
self.__fetch_worker.wait(2000)
|
if worker is not self.__fetch_worker:
|
||||||
self.__fetch_worker.fetchWorkerIsFinished.disconnect(self.onBulletinsFetched)
|
return
|
||||||
self.__fetch_worker.fetchWorkerFinishedWithError.disconnect(self.onBulletinsFetchError)
|
worker.fetchWorkerIsFinished.disconnect(self.onBulletinsFetched)
|
||||||
self.__fetch_worker.deleteLater()
|
worker.fetchWorkerFinishedWithError.disconnect(self.onBulletinsFetchError)
|
||||||
|
worker.wait(2000)
|
||||||
|
worker.deleteLater()
|
||||||
self.__fetch_worker = None
|
self.__fetch_worker = None
|
||||||
|
|
||||||
bulletins = data.get("bulletins", [])
|
bulletins = data.get("bulletins", [])
|
||||||
delete_ids = data.get("delete_ids", [])
|
delete_ids = data.get("delete_ids", [])
|
||||||
merged = self.__bulletin_mgr.updateAndMergeBulletins(bulletins, delete_ids)
|
merged = self.__bulletin_mgr.updateAndMergeBulletins(bulletins, delete_ids)
|
||||||
@@ -336,12 +347,15 @@ class ALBulletinDialog(QDialog, Ui_ALBulletinDialog):
|
|||||||
error_message: str
|
error_message: str
|
||||||
):
|
):
|
||||||
|
|
||||||
if self.__fetch_worker:
|
worker = self.sender()
|
||||||
self.__fetch_worker.wait(2000)
|
if worker is not self.__fetch_worker:
|
||||||
self.__fetch_worker.fetchWorkerIsFinished.disconnect(self.onBulletinsFetched)
|
return
|
||||||
self.__fetch_worker.fetchWorkerFinishedWithError.disconnect(self.onBulletinsFetchError)
|
worker.fetchWorkerIsFinished.disconnect(self.onBulletinsFetched)
|
||||||
self.__fetch_worker.deleteLater()
|
worker.fetchWorkerFinishedWithError.disconnect(self.onBulletinsFetchError)
|
||||||
|
worker.wait(2000)
|
||||||
|
worker.deleteLater()
|
||||||
self.__fetch_worker = None
|
self.__fetch_worker = None
|
||||||
|
|
||||||
self.SyncButton.setEnabled(True)
|
self.SyncButton.setEnabled(True)
|
||||||
self.SyncButton.setText("重试")
|
self.SyncButton.setText("重试")
|
||||||
self.ALSyncStatusLabel.status = ALStatusLabel.Status.FAILURE
|
self.ALSyncStatusLabel.status = ALStatusLabel.Status.FAILURE
|
||||||
@@ -376,6 +390,7 @@ class ALBulletinDialog(QDialog, Ui_ALBulletinDialog):
|
|||||||
if bulletin.get("isNew", False):
|
if bulletin.get("isNew", False):
|
||||||
bulletin["isNew"] = False
|
bulletin["isNew"] = False
|
||||||
widget = self.BulletinListWidget.itemWidget(item)
|
widget = self.BulletinListWidget.itemWidget(item)
|
||||||
|
if widget:
|
||||||
widget.markAsRead()
|
widget.markAsRead()
|
||||||
item.setData(Qt.UserRole, bulletin)
|
item.setData(Qt.UserRole, bulletin)
|
||||||
self.__bulletin_mgr.markBulletinAsRead(bulletin["id"])
|
self.__bulletin_mgr.markBulletinAsRead(bulletin["id"])
|
||||||
|
|||||||
+48
-30
@@ -42,12 +42,14 @@ class ALBulletinPoller(QObject):
|
|||||||
self.__timer.timeout.connect(self.__poll)
|
self.__timer.timeout.connect(self.__poll)
|
||||||
self.__worker = None
|
self.__worker = None
|
||||||
self.__dialog_open = False
|
self.__dialog_open = False
|
||||||
|
self.__stopped = False
|
||||||
self.__mgr = bulletinInstance()
|
self.__mgr = bulletinInstance()
|
||||||
|
|
||||||
def start(
|
def start(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
|
self.__stopped = False
|
||||||
interval_ms = self.__mgr.syncInterval()*60*1000
|
interval_ms = self.__mgr.syncInterval()*60*1000
|
||||||
self.__timer.start(interval_ms)
|
self.__timer.start(interval_ms)
|
||||||
|
|
||||||
@@ -55,6 +57,7 @@ class ALBulletinPoller(QObject):
|
|||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
|
self.__stopped = True
|
||||||
self.__timer.stop()
|
self.__timer.stop()
|
||||||
self.__cleanupWorker()
|
self.__cleanupWorker()
|
||||||
|
|
||||||
@@ -88,6 +91,31 @@ class ALBulletinPoller(QObject):
|
|||||||
|
|
||||||
self.__dialog_open = open
|
self.__dialog_open = open
|
||||||
|
|
||||||
|
def __disconnectWorker(
|
||||||
|
self,
|
||||||
|
worker: ALBulletinFetchWorker
|
||||||
|
):
|
||||||
|
|
||||||
|
try:
|
||||||
|
worker.fetchWorkerIsFinished.disconnect(self.__onFetched)
|
||||||
|
except (TypeError, RuntimeError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
worker.fetchWorkerFinishedWithError.disconnect(self.__onError)
|
||||||
|
except (TypeError, RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __cleanupWorker(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
if self.__worker is None:
|
||||||
|
return
|
||||||
|
self.__disconnectWorker(self.__worker)
|
||||||
|
self.__worker.wait(2000)
|
||||||
|
self.__worker.deleteLater()
|
||||||
|
self.__worker = None
|
||||||
|
|
||||||
def __doFetch(
|
def __doFetch(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
@@ -117,21 +145,28 @@ class ALBulletinPoller(QObject):
|
|||||||
data: dict
|
data: dict
|
||||||
):
|
):
|
||||||
|
|
||||||
if self.__worker is None:
|
worker = self.sender()
|
||||||
|
if worker is not self.__worker:
|
||||||
return
|
return
|
||||||
self.__worker.fetchWorkerIsFinished.disconnect(self.__onFetched)
|
self.__disconnectWorker(worker)
|
||||||
self.__worker.fetchWorkerFinishedWithError.disconnect(self.__onError)
|
worker.wait(2000)
|
||||||
self.__worker.wait(2000)
|
worker.deleteLater()
|
||||||
self.__worker.deleteLater()
|
|
||||||
self.__worker = None
|
self.__worker = None
|
||||||
old_ids = {b["id"] for b in self.__mgr.bulletins()}
|
old_ids = {str(b.get("id", "")) for b in self.__mgr.bulletins()}
|
||||||
bulletins = data.get("bulletins", [])
|
bulletins = data.get("bulletins", [])
|
||||||
delete_ids = data.get("delete_ids", [])
|
delete_ids = data.get("delete_ids", [])
|
||||||
self.__mgr.updateAndMergeBulletins(bulletins, delete_ids)
|
self.__mgr.updateAndMergeBulletins(bulletins, delete_ids)
|
||||||
self.__mgr.setLastSyncTime(datetime.now().astimezone().isoformat())
|
self.__mgr.setLastSyncTime(datetime.now().astimezone().isoformat())
|
||||||
new_ids = {b["id"] for b in bulletins if b["id"] not in old_ids}
|
delete_id_set = {str(d) for d in delete_ids}
|
||||||
|
new_ids = {
|
||||||
|
str(b.get("id", ""))
|
||||||
|
for b in bulletins
|
||||||
|
if str(b.get("id", "")) and str(b.get("id", "")) not in old_ids
|
||||||
|
}
|
||||||
|
new_ids -= delete_id_set
|
||||||
if new_ids:
|
if new_ids:
|
||||||
self.newBulletinsDetected.emit(len(new_ids))
|
self.newBulletinsDetected.emit(len(new_ids))
|
||||||
|
if not self.__stopped:
|
||||||
interval_ms = self.__mgr.syncInterval()*60*1000
|
interval_ms = self.__mgr.syncInterval()*60*1000
|
||||||
self.__timer.start(interval_ms)
|
self.__timer.start(interval_ms)
|
||||||
|
|
||||||
@@ -141,30 +176,13 @@ class ALBulletinPoller(QObject):
|
|||||||
error_message: str
|
error_message: str
|
||||||
):
|
):
|
||||||
|
|
||||||
if self.__worker is None:
|
worker = self.sender()
|
||||||
|
if worker is not self.__worker:
|
||||||
return
|
return
|
||||||
self.__worker.fetchWorkerIsFinished.disconnect(self.__onFetched)
|
self.__disconnectWorker(worker)
|
||||||
self.__worker.fetchWorkerFinishedWithError.disconnect(self.__onError)
|
worker.wait(2000)
|
||||||
self.__worker.wait(2000)
|
worker.deleteLater()
|
||||||
self.__worker.deleteLater()
|
|
||||||
self.__worker = None
|
self.__worker = None
|
||||||
|
if not self.__stopped:
|
||||||
interval_ms = self.__mgr.syncInterval()*60*1000
|
interval_ms = self.__mgr.syncInterval()*60*1000
|
||||||
self.__timer.start(interval_ms)
|
self.__timer.start(interval_ms)
|
||||||
|
|
||||||
def __cleanupWorker(
|
|
||||||
self
|
|
||||||
):
|
|
||||||
|
|
||||||
if self.__worker is None:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self.__worker.fetchWorkerIsFinished.disconnect()
|
|
||||||
except (TypeError, RuntimeError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
self.__worker.fetchWorkerFinishedWithError.disconnect()
|
|
||||||
except (TypeError, RuntimeError):
|
|
||||||
pass
|
|
||||||
self.__worker.wait(2000)
|
|
||||||
self.__worker.deleteLater()
|
|
||||||
self.__worker = None
|
|
||||||
|
|||||||
+48
-16
@@ -458,33 +458,65 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
|
|||||||
self.BulletinTestStatusLabel.setText("请先输入服务器地址。")
|
self.BulletinTestStatusLabel.setText("请先输入服务器地址。")
|
||||||
self.BulletinTestStatusLabel.setStyleSheet("color: red;")
|
self.BulletinTestStatusLabel.setStyleSheet("color: red;")
|
||||||
return
|
return
|
||||||
|
if hasattr(self, '__bulletin_test_worker') and self.__bulletin_test_worker is not None:
|
||||||
|
return
|
||||||
self.BulletinTestButton.setEnabled(False)
|
self.BulletinTestButton.setEnabled(False)
|
||||||
self.BulletinTestStatusLabel.setText("正在测试连接...")
|
self.BulletinTestStatusLabel.setText("正在测试连接...")
|
||||||
self.BulletinTestStatusLabel.setStyleSheet("")
|
self.BulletinTestStatusLabel.setStyleSheet("")
|
||||||
try:
|
self.__bulletin_test_t0 = time.monotonic()
|
||||||
|
from gui.ALBulletinDialog import ALBulletinFetchWorker
|
||||||
api_url = url.rstrip("/") + "/bulletins"
|
api_url = url.rstrip("/") + "/bulletins"
|
||||||
t0 = time.monotonic()
|
self.__bulletin_test_worker = ALBulletinFetchWorker(
|
||||||
response = requests.get(api_url, timeout=5)
|
self, api_url, {"date": "", "time": "", "range_hour": "1"}
|
||||||
elapsed_ms = (time.monotonic() - t0) * 1000
|
)
|
||||||
response.raise_for_status()
|
self.__bulletin_test_worker.fetchWorkerIsFinished.connect(
|
||||||
data = response.json()
|
self.__onBulletinTestFetched
|
||||||
if data.get("code") == 200:
|
)
|
||||||
|
self.__bulletin_test_worker.fetchWorkerFinishedWithError.connect(
|
||||||
|
self.__onBulletinTestError
|
||||||
|
)
|
||||||
|
self.__bulletin_test_worker.start()
|
||||||
|
|
||||||
|
@Slot(dict)
|
||||||
|
def __onBulletinTestFetched(
|
||||||
|
self,
|
||||||
|
data: dict
|
||||||
|
):
|
||||||
|
|
||||||
|
self.__bulletin_test_worker.fetchWorkerIsFinished.disconnect(
|
||||||
|
self.__onBulletinTestFetched
|
||||||
|
)
|
||||||
|
self.__bulletin_test_worker.fetchWorkerFinishedWithError.disconnect(
|
||||||
|
self.__onBulletinTestError
|
||||||
|
)
|
||||||
|
self.__bulletin_test_worker.deleteLater()
|
||||||
|
self.__bulletin_test_worker = None
|
||||||
|
elapsed_ms = (time.monotonic() - self.__bulletin_test_t0) * 1000
|
||||||
self.BulletinTestStatusLabel.setText(
|
self.BulletinTestStatusLabel.setText(
|
||||||
f"连接成功!响应延迟 {elapsed_ms:.0f} ms"
|
f"连接成功!响应延迟 {elapsed_ms:.0f} ms"
|
||||||
)
|
)
|
||||||
self.BulletinTestStatusLabel.setStyleSheet("color: green;")
|
self.BulletinTestStatusLabel.setStyleSheet("color: green;")
|
||||||
else:
|
|
||||||
self.BulletinTestStatusLabel.setText(
|
|
||||||
f"服务器返回异常: [{data.get('code', '?')}] {data.get('msg', '未知错误')}"
|
|
||||||
)
|
|
||||||
self.BulletinTestStatusLabel.setStyleSheet("color: red;")
|
|
||||||
except Exception as e:
|
|
||||||
self.BulletinTestStatusLabel.setText(f"连接失败:{e}")
|
|
||||||
self.BulletinTestStatusLabel.setStyleSheet("color: red;")
|
|
||||||
finally:
|
|
||||||
self.BulletinTestButton.setEnabled(True)
|
self.BulletinTestButton.setEnabled(True)
|
||||||
QTimer.singleShot(3000, self.clearBulletinTestStatus)
|
QTimer.singleShot(3000, self.clearBulletinTestStatus)
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def __onBulletinTestError(
|
||||||
|
self,
|
||||||
|
error_message: str
|
||||||
|
):
|
||||||
|
|
||||||
|
self.__bulletin_test_worker.fetchWorkerIsFinished.disconnect(
|
||||||
|
self.__onBulletinTestFetched
|
||||||
|
)
|
||||||
|
self.__bulletin_test_worker.fetchWorkerFinishedWithError.disconnect(
|
||||||
|
self.__onBulletinTestError
|
||||||
|
)
|
||||||
|
self.__bulletin_test_worker.deleteLater()
|
||||||
|
self.__bulletin_test_worker = None
|
||||||
|
self.BulletinTestStatusLabel.setText(f"连接失败:{error_message}")
|
||||||
|
self.BulletinTestStatusLabel.setStyleSheet("color: red;")
|
||||||
|
self.BulletinTestButton.setEnabled(True)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onCancelButtonClicked(
|
def onCancelButtonClicked(
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ See the LICENSE file for details.
|
|||||||
"""
|
"""
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from interfaces.ConfigProvider import (
|
from interfaces.ConfigProvider import (
|
||||||
@@ -118,13 +118,18 @@ class BulletinManager:
|
|||||||
"""
|
"""
|
||||||
Get auto-sync interval in minutes.
|
Get auto-sync interval in minutes.
|
||||||
|
|
||||||
Values below 1 are clamped to 5 minutes.
|
Values below 1 are clamped upward. Non-integer or missing
|
||||||
|
values fall back to the default of 10 minutes.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Sync interval (minutes), minimum 1.
|
int: Sync interval (minutes), minimum 1.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
interval = self.__cfg.get(CfgKey.GLOBAL.BULLETIN.SYNC_INTERVAL, 10)
|
interval = self.__cfg.get(CfgKey.GLOBAL.BULLETIN.SYNC_INTERVAL, 10)
|
||||||
|
try:
|
||||||
|
interval = int(interval)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return 10
|
||||||
if interval < 1:
|
if interval < 1:
|
||||||
return 5
|
return 5
|
||||||
return interval
|
return interval
|
||||||
@@ -142,6 +147,26 @@ class BulletinManager:
|
|||||||
base = self.serverUrl().rstrip("/")
|
base = self.serverUrl().rstrip("/")
|
||||||
return f"{base}/bulletins"
|
return f"{base}/bulletins"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ensureAware(
|
||||||
|
dt_value: datetime
|
||||||
|
) -> datetime:
|
||||||
|
|
||||||
|
if dt_value.tzinfo is None:
|
||||||
|
return dt_value.replace(tzinfo=timezone.utc)
|
||||||
|
return dt_value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parseBulletinDateTime(
|
||||||
|
b: dict
|
||||||
|
) -> datetime:
|
||||||
|
|
||||||
|
raw = b.get("dateTime", "")
|
||||||
|
if not raw:
|
||||||
|
raise ValueError("empty dateTime")
|
||||||
|
dt_value = datetime.fromisoformat(raw)
|
||||||
|
return BulletinManager._ensureAware(dt_value)
|
||||||
|
|
||||||
def isFirstSync(
|
def isFirstSync(
|
||||||
self
|
self
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -152,6 +177,7 @@ class BulletinManager:
|
|||||||
bool: True if no cached bulletins or no last sync time.
|
bool: True if no cached bulletins or no last sync time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
with self.__lock:
|
||||||
last = self.lastSyncTime()
|
last = self.lastSyncTime()
|
||||||
bulletins = self.bulletins()
|
bulletins = self.bulletins()
|
||||||
return last is None or not bulletins
|
return last is None or not bulletins
|
||||||
@@ -169,12 +195,15 @@ class BulletinManager:
|
|||||||
bool: True if a full sync should be performed.
|
bool: True if a full sync should be performed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
with self.__lock:
|
||||||
last = self.lastSyncTime()
|
last = self.lastSyncTime()
|
||||||
if last is None:
|
if last is None:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
last_dt = datetime.fromisoformat(last)
|
last_dt = datetime.fromisoformat(last)
|
||||||
return (datetime.now().astimezone() - last_dt) > timedelta(hours=1)
|
last_dt = self._ensureAware(last_dt)
|
||||||
|
now = datetime.now(timezone.utc).astimezone()
|
||||||
|
return (now - last_dt) > timedelta(hours=1)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -184,25 +213,35 @@ class BulletinManager:
|
|||||||
"""
|
"""
|
||||||
Calculate the date / time / range_hour query parameters.
|
Calculate the date / time / range_hour query parameters.
|
||||||
|
|
||||||
|
Falls back to a first-sync (7-day window) on any parsing error.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Keys "date", "time", "range_hour" for the API request.
|
dict: Keys "date", "time", "range_hour" for the API request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc).astimezone()
|
||||||
|
try:
|
||||||
if self.isFirstSync():
|
if self.isFirstSync():
|
||||||
start_date = datetime.now() - timedelta(days=7)
|
start_date = now - timedelta(days=7)
|
||||||
range_hour = str(24 * (7 + 1))
|
range_hour = str(24 * 8)
|
||||||
elif self.shouldFullSync():
|
elif self.shouldFullSync():
|
||||||
|
with self.__lock:
|
||||||
bulletins = self.bulletins()
|
bulletins = self.bulletins()
|
||||||
earliest = min(bulletins, key=lambda x: x.get("dateTime", ""))
|
earliest = min(bulletins, key=self._parseBulletinDateTime)
|
||||||
start_date = datetime.fromisoformat(earliest["dateTime"])
|
start_date = self._parseBulletinDateTime(earliest)
|
||||||
diff = datetime.now().astimezone() - start_date
|
diff = now - start_date
|
||||||
range_hour = str(int(diff.total_seconds() / 3600) + 1)
|
range_hour = str(int(diff.total_seconds() / 3600) + 1)
|
||||||
else:
|
else:
|
||||||
|
with self.__lock:
|
||||||
bulletins = self.bulletins()
|
bulletins = self.bulletins()
|
||||||
latest = max(bulletins, key=lambda x: x.get("dateTime", ""))
|
latest = max(bulletins, key=self._parseBulletinDateTime)
|
||||||
start_date = datetime.fromisoformat(latest["dateTime"])
|
start_date = self._parseBulletinDateTime(latest)
|
||||||
diff = datetime.now().astimezone() - start_date
|
diff = now - start_date
|
||||||
range_hour = str(int(diff.total_seconds() / 3600) + 1)
|
range_hour = str(int(diff.total_seconds() / 3600) + 1)
|
||||||
|
except (ValueError, TypeError, KeyError):
|
||||||
|
start_date = now - timedelta(days=7)
|
||||||
|
range_hour = str(24 * 8)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"date": start_date.strftime("%Y-%m-%d"),
|
"date": start_date.strftime("%Y-%m-%d"),
|
||||||
"time": start_date.strftime("%H:%M:%S"),
|
"time": start_date.strftime("%H:%M:%S"),
|
||||||
@@ -219,7 +258,7 @@ class BulletinManager:
|
|||||||
|
|
||||||
New bulletins are added with isNew=True. Existing bulletins
|
New bulletins are added with isNew=True. Existing bulletins
|
||||||
keep their current isNew state. Entries listed in delete_ids
|
keep their current isNew state. Entries listed in delete_ids
|
||||||
are removed.
|
are removed. Bulletins missing an "id" field are skipped.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
new_bulletins (list[dict]): Incoming bulletin list.
|
new_bulletins (list[dict]): Incoming bulletin list.
|
||||||
@@ -230,22 +269,33 @@ class BulletinManager:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
delete_set = set(delete_ids)
|
delete_set = set(str(d) for d in delete_ids)
|
||||||
bulletins_dict = {b["id"]: b for b in self.bulletins()}
|
bulletins_dict = {}
|
||||||
|
for b in self.bulletins():
|
||||||
|
bid = b.get("id")
|
||||||
|
if bid is not None:
|
||||||
|
bulletins_dict[str(bid)] = b
|
||||||
|
|
||||||
for bulletin in new_bulletins:
|
for bulletin in new_bulletins:
|
||||||
bid = bulletin["id"]
|
bid = bulletin.get("id")
|
||||||
|
if bid is None:
|
||||||
|
continue
|
||||||
|
bid = str(bid)
|
||||||
if bid in delete_set:
|
if bid in delete_set:
|
||||||
bulletins_dict.pop(bid, None)
|
bulletins_dict.pop(bid, None)
|
||||||
continue
|
continue
|
||||||
if bid not in bulletins_dict:
|
bulletin["isNew"] = (
|
||||||
bulletin["isNew"] = True
|
True
|
||||||
else:
|
if bid not in bulletins_dict
|
||||||
bulletin["isNew"] = bulletins_dict[bid].get("isNew", True)
|
else bulletins_dict[bid].get("isNew", True)
|
||||||
|
)
|
||||||
bulletins_dict[bid] = bulletin
|
bulletins_dict[bid] = bulletin
|
||||||
|
|
||||||
for bid in delete_set:
|
for bid in delete_set:
|
||||||
bulletins_dict.pop(bid, None)
|
bulletins_dict.pop(bid, None)
|
||||||
|
|
||||||
result = list(bulletins_dict.values())
|
result = list(bulletins_dict.values())
|
||||||
result.sort(key=lambda x: int(x["id"]))
|
result.sort(key=lambda x: str(x.get("id", "")))
|
||||||
self.setBulletins(result)
|
self.setBulletins(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -263,7 +313,7 @@ class BulletinManager:
|
|||||||
with self.__lock:
|
with self.__lock:
|
||||||
bulletins = self.bulletins()
|
bulletins = self.bulletins()
|
||||||
for b in bulletins:
|
for b in bulletins:
|
||||||
if b["id"] == bulletin_id:
|
if str(b.get("id", "")) == str(bulletin_id):
|
||||||
b["isNew"] = False
|
b["isNew"] = False
|
||||||
self.setBulletins(bulletins)
|
self.setBulletins(bulletins)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class ConfigManager:
|
|||||||
config_data = JSONReader(config_path).data()
|
config_data = JSONReader(config_path).data()
|
||||||
self.__config_data[config_type.value] = config_data
|
self.__config_data[config_type.value] = config_data
|
||||||
return
|
return
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self.__config_data[config_type.value] = ConfigTemplate(config_type).template()
|
self.__config_data[config_type.value] = ConfigTemplate(config_type).template()
|
||||||
JSONWriter(config_path, self.__config_data[config_type.value])
|
JSONWriter(config_path, self.__config_data[config_type.value])
|
||||||
|
|||||||
Reference in New Issue
Block a user