玉兔远程控制 0.1.0-bate8
载入中...
搜索中...
未找到
FrmExtensionStore.cpp
1// Author: Kang Lin <kl222@126.com>
2
3#include <QFileDialog>
4#include <QLoggingCategory>
5#include <QVBoxLayout>
6#include <QHBoxLayout>
7#include <QHeaderView>
8#include <QPushButton>
9#include <QTableView>
10#include <QLineEdit>
11#include <QLabel>
12#include <QProgressBar>
13#include <QMessageBox>
14#include <QStandardItemModel>
15#include <QNetworkAccessManager>
16#include <QNetworkRequest>
17#include <QNetworkReply>
18#include <QUrl>
19#include <QJsonDocument>
20#include <QJsonObject>
21#include <QJsonArray>
22#include <QFile>
23#include <QDir>
24#include <QSettings>
25#include <QDateTime>
26#include <QUuid>
27#include <QDesktopServices>
28#include <QStandardPaths>
29#include <QCryptographicHash>
30
31#include "FrmExtensionStore.h"
32#include "FrmExtensionManager.h"
33#include "ParameterWebBrowser.h"
34#include "ui_FrmExtensionStore.h"
35
36static Q_LOGGING_CATEGORY(log, "WebBrowser.ExtensionStore")
37
38// 默认扩展商城 API 地址
39const QString DEFAULT_STORE_API = "https://api.github.com/repos/KangLin/RabbitRemoteControl/contents/ExtensionStore";
40
42 : QWidget(parent)
43 , ui(new Ui::CFrmExtensionStore)
44 , m_pNetworkManager(nullptr)
45 , m_pExtensionManager(nullptr)
46 , m_pPara(nullptr)
47 , m_pModelExtensions(nullptr)
48 , m_apiBaseUrl(DEFAULT_STORE_API)
49{
50 ui->setupUi(this);
51 setWindowTitle(tr("Chrome Extension Store"));
52 setWindowIcon(QIcon::fromTheme("store"));
53
54 InitializeUI();
55 SetupNetworkManager();
56 SetupConnections();
57
58 qDebug(log) << Q_FUNC_INFO << "Extension store initialized";
59}
60
61CFrmExtensionStore::~CFrmExtensionStore()
62{
63 qDebug(log) << Q_FUNC_INFO;
64
65 // 取消所有下载
66 for(auto reply : m_downloads) {
67 if(reply) {
68 reply->abort();
69 reply->deleteLater();
70 }
71 }
72
73 delete ui;
74}
75
76void CFrmExtensionStore::InitializeUI()
77{
78 // 创建扩展列表模型
79 m_pModelExtensions = new QStandardItemModel(this);
80 m_pModelExtensions->setColumnCount(7);
81
82 // 设置表头
83 QStringList headers;
84 headers << tr("Icon") << tr("Name") << tr("Version")
85 << tr("Rating") << tr("Downloads") << tr("ID") << tr("Status");
86 m_pModelExtensions->setHorizontalHeaderLabels(headers);
87
88 // 配置表视图
89 ui->tvExtensions->setModel(m_pModelExtensions);
90 ui->tvExtensions->setSelectionMode(QAbstractItemView::SingleSelection);
91 ui->tvExtensions->setSelectionBehavior(QAbstractItemView::SelectRows);
92 ui->tvExtensions->setEditTriggers(QAbstractItemView::NoEditTriggers);
93 ui->tvExtensions->setContextMenuPolicy(Qt::CustomContextMenu);
94 ui->tvExtensions->setAlternatingRowColors(true);
95
96 // 设置列宽
97 ui->tvExtensions->horizontalHeader()->setStretchLastSection(true);
98 ui->tvExtensions->verticalHeader()->setDefaultSectionSize(24);
99
100 // 配置搜索框
101 ui->leSearch->setPlaceholderText(tr("Search extensions..."));
102
103 // 配置按钮
104 ui->pbSearch->setText(tr("Search"));
105 ui->pbPopular->setText(tr("Popular"));
106 ui->pbRecommended->setText(tr("Recommended"));
107 ui->pbDownload->setText(tr("Download"));
108 ui->pbInstall->setText(tr("Install"));
109 ui->pbCancel->setText(tr("Cancel"));
110 ui->pbDetails->setText(tr("Details"));
111 ui->pbRefresh->setText(tr("Refresh"));
112 ui->pbClearCache->setText(tr("Clear Cache"));
113
114 // 配置详情面板
115 ui->lblExtensionName->setText(tr("Extension Name"));
116 ui->lblExtensionDesc->setWordWrap(true);
117
118 // 进度条
119 ui->progressDownload->setVisible(false);
120
121 qDebug(log) << "UI initialized";
122}
123
124void CFrmExtensionStore::SetupNetworkManager()
125{
126 m_pNetworkManager = new QNetworkAccessManager(this);
127 qDebug(log) << "Network manager setup completed";
128}
129
130void CFrmExtensionStore::SetupConnections()
131{
132 bool check = true;
133
134 // 按钮连接
135 check = connect(ui->pbSearch, &QPushButton::clicked,
136 this, &CFrmExtensionStore::on_pbSearch_clicked);
137 check = connect(ui->pbPopular, &QPushButton::clicked,
138 this, &CFrmExtensionStore::on_pbPopular_clicked);
139 check = connect(ui->pbRecommended, &QPushButton::clicked,
140 this, &CFrmExtensionStore::on_pbRecommended_clicked);
141 check = connect(ui->pbDownload, &QPushButton::clicked,
142 this, &CFrmExtensionStore::on_pbDownload_clicked);
143 check = connect(ui->pbInstall, &QPushButton::clicked,
144 this, &CFrmExtensionStore::on_pbInstall_clicked);
145 check = connect(ui->pbCancel, &QPushButton::clicked,
146 this, &CFrmExtensionStore::on_pbCancel_clicked);
147 check = connect(ui->pbDetails, &QPushButton::clicked,
148 this, &CFrmExtensionStore::on_pbDetails_clicked);
149 check = connect(ui->pbRefresh, &QPushButton::clicked,
150 this, &CFrmExtensionStore::on_pbRefresh_clicked);
151 check = connect(ui->pbClearCache, &QPushButton::clicked,
152 this, &CFrmExtensionStore::on_pbClearCache_clicked);
153
154 // 表视图连接
155 check = connect(ui->tvExtensions->selectionModel(), &QItemSelectionModel::selectionChanged,
156 this, &CFrmExtensionStore::slotExtensionSelected);
157 check = connect(ui->tvExtensions, &QTableView::customContextMenuRequested,
158 this, &CFrmExtensionStore::slotCustomContextMenu);
159
160 // 回车搜索
161 check = connect(ui->leSearch, &QLineEdit::returnPressed,
162 this, &CFrmExtensionStore::on_pbSearch_clicked);
163
164 Q_ASSERT(check);
165 qDebug(log) << "Connections setup completed";
166}
167
169{
170 m_pExtensionManager = manager;
171 if(!m_pExtensionManager) {
172 qWarning(log) << "Failed to set extension manager: manager is null";
173 return -1;
174 }
175
176 qDebug(log) << "Extension manager set successfully";
177 return 0;
178}
179
180void CFrmExtensionStore::SetAPIBaseUrl(const QString &baseUrl)
181{
182 m_apiBaseUrl = baseUrl;
183 qDebug(log) << "API base URL set to:" << m_apiBaseUrl;
184}
185
186void CFrmExtensionStore::SearchExtensions(const QString &keyword)
187{
188 qDebug(log) << Q_FUNC_INFO << "Keyword:" << keyword;
189
190 if(!m_pNetworkManager) return;
191
192 // 构建搜索 URL
193 QString searchUrl = m_apiBaseUrl + "/search?q=" + keyword;
194
195 QNetworkRequest request(QUrl(searchUrl));
196/*
197 request.setHeader(QNetworkRequest::UserAgentHeader, "RabbitRemoteControl");
198
199 QNetworkReply* reply = m_pNetworkManager->get(request);
200
201 connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
202 this, &CFrmExtensionStore::slotNetworkError);
203
204 connect(reply, &QNetworkReply::finished, this, [this, reply]() {
205 if(reply->error() == QNetworkReply::NoError) {
206 QByteArray data = reply->readAll();
207 auto extensions = ParseExtensionList(data);
208
209 ClearExtensionList();
210
211 for(const auto &ext : extensions) {
212 AddExtensionItem(ext.toObject());
213 }
214
215 qDebug(log) << "Search completed, found:" << extensions.count();
216 }
217 reply->deleteLater();
218 });
219*/
220}
221
223{
224 qDebug(log) << Q_FUNC_INFO;
225
226 if(!m_pNetworkManager) return;
227
228 QString url = m_apiBaseUrl + "/popular";
229 /*
230 QNetworkRequest request(QUrl(url));
231 request.setHeader(QNetworkRequest::UserAgentHeader, "RabbitRemoteControl");
232
233 QNetworkReply* reply = m_pNetworkManager->get(request);
234
235 connect(reply, &QNetworkReply::finished, this, [this, reply]() {
236 if(reply->error() == QNetworkReply::NoError) {
237 QByteArray data = reply->readAll();
238 auto extensions = ParseExtensionList(data);
239
240 ClearExtensionList();
241
242 for(const auto &ext : extensions) {
243 AddExtensionItem(ext.toObject());
244 }
245
246 qDebug(log) << "Popular extensions loaded:" << extensions.count();
247 }
248 reply->deleteLater();
249 });
250 */
251}
252
254{
255 qDebug(log) << Q_FUNC_INFO;
256
257 if(!m_pNetworkManager) return;
258
259 QString url = m_apiBaseUrl + "/recommended";
260 /*
261 QNetworkRequest request(QUrl(url));
262 request.setHeader(QNetworkRequest::UserAgentHeader, "RabbitRemoteControl");
263
264 QNetworkReply* reply = m_pNetworkManager->get(request);
265
266 connect(reply, &QNetworkReply::finished, this, [this, reply]() {
267 if(reply->error() == QNetworkReply::NoError) {
268 QByteArray data = reply->readAll();
269 auto extensions = ParseExtensionList(data);
270
271 ClearExtensionList();
272
273 for(const auto &ext : extensions) {
274 AddExtensionItem(ext.toObject());
275 }
276
277 qDebug(log) << "Recommended extensions loaded:" << extensions.count();
278 }
279 reply->deleteLater();
280 });
281 */
282}
283
284QString CFrmExtensionStore::DownloadExtension(const QString &extensionId)
285{
286 qDebug(log) << Q_FUNC_INFO << "Extension ID:" << extensionId;
287
288 if(!m_pNetworkManager) return QString();
289
290 // 生成下载 ID
291 QString downloadId = GenerateDownloadId();
292
293 // 获取扩展下载 URL
294 QString downloadUrl = GetExtensionFileUrl(extensionId);
295 /*
296 QNetworkRequest request(QUrl(downloadUrl));
297 request.setHeader(QNetworkRequest::UserAgentHeader, "RabbitRemoteControl");
298
299 QNetworkReply* reply = m_pNetworkManager->get(request);
300
301 // 保存下载关系
302 m_downloads[downloadId] = reply;
303 m_downloadExtensionId[downloadId] = extensionId;
304
305 // 连接进度信号
306 connect(reply, QOverload<qint64, qint64>::of(&QNetworkReply::downloadProgress),
307 this, &CFrmExtensionStore::slotDownloadProgress);
308
309 connect(reply, &QNetworkReply::finished, this, [this, reply, downloadId, extensionId]() {
310 if(reply->error() == QNetworkReply::NoError) {
311 // 保存文件
312 QByteArray data = reply->readAll();
313 QString filePath = GetDownloadPath() + "/" + extensionId + ".crx";
314
315 QFile file(filePath);
316 if(file.open(QIODevice::WriteOnly)) {
317 file.write(data);
318 file.close();
319
320 qDebug(log) << "Extension downloaded:" << filePath;
321 QMessageBox::information(this, tr("Success"),
322 tr("Extension downloaded successfully!\nPath: %1").arg(filePath));
323
324 ProcessDownloadedFile(filePath, extensionId);
325 }
326 } else {
327 qWarning(log) << "Download failed:" << reply->errorString();
328 QMessageBox::warning(this, tr("Error"),
329 tr("Failed to download extension: %1").arg(reply->errorString()));
330 }
331
332 reply->deleteLater();
333 m_downloads.remove(downloadId);
334 m_downloadExtensionId.remove(downloadId);
335 });
336 */
337 return downloadId;
338}
339
340void CFrmExtensionStore::CancelDownload(const QString &downloadId)
341{
342 qDebug(log) << Q_FUNC_INFO << "Download ID:" << downloadId;
343
344 if(m_downloads.contains(downloadId)) {
345 QNetworkReply* reply = m_downloads[downloadId];
346 if(reply) {
347 reply->abort();
348 reply->deleteLater();
349 }
350 m_downloads.remove(downloadId);
351 m_downloadExtensionId.remove(downloadId);
352 }
353}
354
355int CFrmExtensionStore::GetDownloadProgress(const QString &downloadId) const
356{
357 // 可以添加进度跟踪逻辑
358 return 0;
359}
360
361void CFrmExtensionStore::on_pbSearch_clicked()
362{
363 QString keyword = ui->leSearch->text();
364 if(keyword.isEmpty()) {
365 QMessageBox::warning(this, tr("Warning"), tr("Please enter a search keyword"));
366 return;
367 }
368
369 SearchExtensions(keyword);
370}
371
372void CFrmExtensionStore::on_pbPopular_clicked()
373{
375}
376
377void CFrmExtensionStore::on_pbRecommended_clicked()
378{
380}
381
382void CFrmExtensionStore::on_pbDownload_clicked()
383{
384 auto indexes = ui->tvExtensions->selectionModel()->selectedRows();
385 if(indexes.isEmpty()) {
386 QMessageBox::warning(this, tr("Warning"), tr("Please select an extension"));
387 return;
388 }
389
390 int row = indexes.at(0).row();
391 auto idItem = m_pModelExtensions->item(row, ColumnNo::ID);
392 auto nameItem = m_pModelExtensions->item(row, ColumnNo::Name);
393
394 if(!idItem || !nameItem) return;
395
396 QString extensionId = idItem->text();
397
398 int ret = QMessageBox::question(this, tr("Confirm"),
399 tr("Do you want to download '%1'?").arg(nameItem->text()),
400 QMessageBox::Yes | QMessageBox::No);
401
402 if(ret == QMessageBox::Yes) {
403 QString downloadId = DownloadExtension(extensionId);
404 if(!downloadId.isEmpty()) {
405 ui->progressDownload->setVisible(true);
406 ui->progressDownload->setValue(0);
407 }
408 }
409}
410
411void CFrmExtensionStore::on_pbInstall_clicked()
412{
413 qDebug(log) << Q_FUNC_INFO;
414
415 if(!m_pExtensionManager) {
416 QMessageBox::warning(this, tr("Error"),
417 tr("Extension manager not set"));
418 return;
419 }
420
421 // 打开文件选择对话框
422 QString filter = tr("Chrome Extension (*.crx);;Extension Folder;;All Files (*)");
423 QString path = QFileDialog::getOpenFileName(
424 this, tr("Select Downloaded Extension"), GetDownloadPath(), filter);
425
426 if(!path.isEmpty()) {
427 m_pExtensionManager->InstallExtension(path);
428 }
429}
430
431void CFrmExtensionStore::on_pbCancel_clicked()
432{
433 auto indexes = ui->tvExtensions->selectionModel()->selectedRows();
434 if(indexes.isEmpty()) return;
435
436 // 取消所有下载
437 for(auto downloadId : m_downloads.keys()) {
438 CancelDownload(downloadId);
439 }
440
441 ui->progressDownload->setVisible(false);
442 QMessageBox::information(this, tr("Info"), tr("All downloads cancelled"));
443}
444
445void CFrmExtensionStore::on_pbDetails_clicked()
446{
447 qDebug(log) << Q_FUNC_INFO;
448
449 auto indexes = ui->tvExtensions->selectionModel()->selectedRows();
450 if(indexes.isEmpty()) return;
451
452 int row = indexes.at(0).row();
453 auto idItem = m_pModelExtensions->item(row, ColumnNo::ID);
454
455 if(!idItem) return;
456
457 QString extensionId = idItem->text();
458
459 if(m_extensionCache.contains(extensionId)) {
460 DisplayExtensionDetails(m_extensionCache[extensionId]);
461 }
462}
463
464void CFrmExtensionStore::on_pbRefresh_clicked()
465{
467}
468
469void CFrmExtensionStore::on_pbClearCache_clicked()
470{
471 qDebug(log) << Q_FUNC_INFO;
472
473 QString cachePath = GetCachePath();
474 QDir dir(cachePath);
475
476 if(dir.exists()) {
477 dir.removeRecursively();
478 qDebug(log) << "Cache cleared:" << cachePath;
479 QMessageBox::information(this, tr("Success"), tr("Cache cleared"));
480 }
481}
482
483void CFrmExtensionStore::slotExtensionSelected()
484{
485 auto indexes = ui->tvExtensions->selectionModel()->selectedRows();
486 if(indexes.isEmpty()) return;
487
488 int row = indexes.at(0).row();
489 auto idItem = m_pModelExtensions->item(row, ColumnNo::ID);
490
491 if(idItem && m_extensionCache.contains(idItem->text())) {
492 DisplayExtensionDetails(m_extensionCache[idItem->text()]);
493 }
494}
495
496void CFrmExtensionStore::slotCustomContextMenu(const QPoint &pos)
497{
498 qDebug(log) << Q_FUNC_INFO;
499 // TODO: 实现右键菜单
500}
501
502void CFrmExtensionStore::slotDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
503{
504 if(bytesTotal > 0) {
505 int progress = (bytesReceived * 100) / bytesTotal;
506 ui->progressDownload->setValue(progress);
507
508 qDebug(log) << "Download progress:" << progress << "%";
509 }
510}
511
512void CFrmExtensionStore::slotSearchFinished()
513{
514 qDebug(log) << Q_FUNC_INFO;
515}
516
517void CFrmExtensionStore::slotDownloadFinished()
518{
519 qDebug(log) << Q_FUNC_INFO;
520}
521
522void CFrmExtensionStore::slotNetworkError(QNetworkReply::NetworkError error)
523{
524 qWarning(log) << "Network error:" << error;
525 QMessageBox::warning(this, tr("Network Error"),
526 tr("Failed to connect to extension store"));
527}
528
529void CFrmExtensionStore::RefreshExtensionList()
530{
532}
533
534int CFrmExtensionStore::AddExtensionItem(const QJsonObject &extInfo)
535{
536 if(!m_pModelExtensions) return -1;
537
538 QString extensionId = extInfo.value("id").toString();
539 QString name = extInfo.value("name").toString();
540 QString version = extInfo.value("version").toString("1.0.0");
541 double rating = extInfo.value("rating").toDouble(4.5);
542 int downloads = extInfo.value("downloads").toInt(0);
543
544 // 保存到缓存
545 m_extensionCache[extensionId] = extInfo;
546
547 int row = m_pModelExtensions->rowCount();
548 m_pModelExtensions->insertRow(row);
549
550 // 图标
551 auto itemIcon = new QStandardItem();
552 itemIcon->setEditable(false);
553 m_pModelExtensions->setItem(row, ColumnNo::Icon, itemIcon);
554
555 // 名称
556 auto itemName = new QStandardItem(name);
557 itemName->setEditable(false);
558 m_pModelExtensions->setItem(row, ColumnNo::Name, itemName);
559
560 // 版本
561 auto itemVersion = new QStandardItem(version);
562 itemVersion->setEditable(false);
563 m_pModelExtensions->setItem(row, ColumnNo::Version, itemVersion);
564
565 // 评分
566 auto itemRating = new QStandardItem(QString::number(rating, 'f', 1));
567 itemRating->setEditable(false);
568 m_pModelExtensions->setItem(row, ColumnNo::Rating, itemRating);
569
570 // 下载数
571 auto itemDownloads = new QStandardItem(QString::number(downloads));
572 itemDownloads->setEditable(false);
573 m_pModelExtensions->setItem(row, ColumnNo::Downloads, itemDownloads);
574
575 // ID
576 auto itemId = new QStandardItem(extensionId);
577 itemId->setEditable(false);
578 m_pModelExtensions->setItem(row, ColumnNo::ID, itemId);
579
580 // 状态
581 QString status = tr("Not Installed");
582 if(m_pExtensionManager) {
583 auto installed = m_pExtensionManager->GetInstalledExtensions();
584 foreach (auto e, installed) {
585 if(e.id() == extensionId)
586 status = tr("Installed");
587 }
588 }
589
590 auto itemStatus = new QStandardItem(status);
591 itemStatus->setEditable(false);
592 m_pModelExtensions->setItem(row, ColumnNo::Status, itemStatus);
593
594 qDebug(log) << "Added extension:" << name << "ID:" << extensionId;
595 return 0;
596}
597
598void CFrmExtensionStore::DisplayExtensionDetails(const QJsonObject &extInfo)
599{
600 QString name = extInfo.value("name").toString();
601 QString description = extInfo.value("description").toString();
602 QString version = extInfo.value("version").toString();
603 double rating = extInfo.value("rating").toDouble(4.5);
604 int downloads = extInfo.value("downloads").toInt(0);
605 QString author = extInfo.value("author").toString();
606
607 ui->lblExtensionName->setText(name);
608 ui->lblExtensionDesc->setText(description);
609
610 QString details = QString(
611 "<b>%1</b><br>"
612 "Version: %2<br>"
613 "Rating: %3 ⭐<br>"
614 "Downloads: %4<br>"
615 "Author: %5"
616 ).arg(name, version, QString::number(rating, 'f', 1),
617 QString::number(downloads), author);
618
619 ui->lblExtensionDesc->setText(details);
620}
621
622void CFrmExtensionStore::ClearExtensionList()
623{
624 if(m_pModelExtensions) {
625 m_pModelExtensions->removeRows(0, m_pModelExtensions->rowCount());
626 }
627}
628
629int CFrmExtensionStore::ProcessDownloadedFile(const QString &filePath,
630 const QString &extensionId)
631{
632 qDebug(log) << Q_FUNC_INFO << "File:" << filePath;
633
634 if(!m_pExtensionManager) return -1;
635
636 // 自动安装下载的扩展
637 int ret = QMessageBox::question(this, tr("Install Now?"),
638 tr("Do you want to install this extension now?"),
639 QMessageBox::Yes | QMessageBox::No);
640
641 if(ret == QMessageBox::Yes) {
642 m_pExtensionManager->InstallExtension(filePath);
643 }
644
645 return -1;
646}
647
648QString CFrmExtensionStore::GetDownloadPath() const
649{
650 QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)
651 + "/RabbitRemoteControl/Extensions";
652
653 QDir dir;
654 if(!dir.exists(path)) {
655 dir.mkpath(path);
656 }
657
658 return path;
659}
660
661QString CFrmExtensionStore::GetCachePath() const
662{
663 QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
664 + "/ExtensionStore";
665
666 QDir dir;
667 if(!dir.exists(path)) {
668 dir.mkpath(path);
669 }
670
671 return path;
672}
673
674int CFrmExtensionStore::SaveToCache(const QString &key, const QJsonObject &data)
675{
676 QString cachePath = GetCachePath() + "/" + key + ".json";
677
678 QJsonDocument doc(data);
679 QFile file(cachePath);
680
681 if(file.open(QIODevice::WriteOnly)) {
682 file.write(doc.toJson());
683 file.close();
684 qDebug(log) << "Cached:" << key;
685 return 0;
686 }
687
688 return -1;
689}
690
691QJsonObject CFrmExtensionStore::LoadFromCache(const QString &key) const
692{
693 QString cachePath = GetCachePath() + "/" + key + ".json";
694
695 QFile file(cachePath);
696 if(file.open(QIODevice::ReadOnly)) {
697 QByteArray data = file.readAll();
698 file.close();
699
700 QJsonDocument doc = QJsonDocument::fromJson(data);
701 return doc.object();
702 }
703
704 return QJsonObject();
705}
706
707bool CFrmExtensionStore::IsCacheValid(const QString &key) const
708{
709 QString cachePath = GetCachePath() + "/" + key + ".json";
710
711 QFile file(cachePath);
712 if(!file.exists()) return false;
713
714 QFileInfo fi(cachePath);
715 QDateTime lastModified = fi.lastModified();
716 QDateTime now = QDateTime::currentDateTime();
717
718 int hours = lastModified.secsTo(now) / 3600;
719 return hours < CACHE_VALIDITY_HOURS;
720}
721
722QJsonArray CFrmExtensionStore::ParseExtensionList(const QByteArray &data) const
723{
724 QJsonDocument doc = QJsonDocument::fromJson(data);
725
726 if(doc.isArray()) {
727 return doc.array();
728 } else if(doc.isObject()) {
729 QJsonObject obj = doc.object();
730 if(obj.contains("extensions")) {
731 return obj.value("extensions").toArray();
732 }
733 }
734
735 return QJsonArray();
736}
737
738QJsonObject CFrmExtensionStore::ParseExtensionDetails(const QByteArray &data) const
739{
740 QJsonDocument doc = QJsonDocument::fromJson(data);
741 return doc.object();
742}
743
744QString CFrmExtensionStore::GenerateDownloadId() const
745{
746 return QUuid::createUuid().toString(QUuid::WithoutBraces);
747}
748
749bool CFrmExtensionStore::IsExtensionIdValid(const QString &id) const
750{
751 return !id.isEmpty() && id.length() >= 32;
752}
753
754QString CFrmExtensionStore::GetExtensionFileUrl(const QString &extensionId) const
755{
756 // 从 GitHub 或自定义服务器下载
757 return m_apiBaseUrl + "/extensions/" + extensionId + ".crx";
758}
759
760QString CFrmExtensionStore::GetChromeWebStoreUrl(const QString &extensionId) const
761{
762 return "https://chromewebstore.google.com/detail/" + extensionId;
763}
扩展管理器界面
QList< QWebEngineExtensionInfo > GetInstalledExtensions() const
获取已安装的扩展列表
void InstallExtension(const QString &path)
安装或加载 Chrome 扩展
Chrome 扩展商城
void CancelDownload(const QString &downloadId)
取消下载
void SearchExtensions(const QString &keyword)
搜索扩展
QString DownloadExtension(const QString &extensionId)
下载扩展
int GetDownloadProgress(const QString &downloadId) const
获取下载进度
void GetRecommendedExtensions()
获取推荐扩展列表
int SetExtensionManager(CFrmExtensionManager *manager)
设置扩展管理器
void SetAPIBaseUrl(const QString &baseUrl)
设置基础 URL (可以指向自建服务器)
void GetPopularExtensions()
获取热门扩展列表