poi-plugin-remodel-assistant 1.2.3-beta.6 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -17
- package/data/changelog.json +4 -2
- package/i18n/en-US.json +8 -2
- package/i18n/ja-JP.json +8 -2
- package/i18n/zh-CN.json +8 -2
- package/i18n/zh-TW.json +8 -2
- package/package.json +2 -2
- package/views/new-interface/app/new-interface-area.js +4 -4
- package/views/new-interface/data/modals/problem-diagnostics-modal.js +29 -39
- package/views/shared/native-file-dialog.js +15 -1
- package/views/shared/problem-diagnostics.js +274 -259
package/README.md
CHANGED
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
# 改修助手
|
|
2
2
|
|
|
3
|
-
Poi
|
|
3
|
+
Poi plugin for planning Kantai Collection equipment improvements.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Tell the plugin the equipment you ultimately want, the target improvement level, and quantity. It will combine that goal with your current inventory to organize routes, intermediate equipment, material requirements, and what you can improve today.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- Review acquisition sources
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
9
|
+
- Create improvement plans directly from **Equipment library**.
|
|
10
|
+
- Match plans against your current inventory to determine the practical starting point.
|
|
11
|
+
- Manage complete improvement routes, with explicit user choice when more than one valid route exists.
|
|
12
|
+
- Review acquisition sources for missing prerequisite equipment, including development, quests, and ship initial/remodel equipment when available.
|
|
13
|
+
- Use **Today** to see plans that can be advanced now, together with assistant ships, available days, materials, and remaining routes.
|
|
14
|
+
- Filter and sort equipment, manage low-priority equipment, and customize ordinary detail sections.
|
|
15
|
+
- Back up and restore plans, route choices, and interface settings.
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
|
-
1. Open **Equipment library
|
|
20
|
-
2.
|
|
21
|
-
3.
|
|
19
|
+
1. Open **Equipment library** and select your target equipment.
|
|
20
|
+
2. Set the target improvement level and quantity, then create a plan.
|
|
21
|
+
3. If more than one route is available, choose the route you want.
|
|
22
|
+
4. Open **Today** and follow the current guidance.
|
|
22
23
|
|
|
23
|
-
The in-plugin **Help** entry contains
|
|
24
|
+
The in-plugin **Help** entry contains illustrated guides.
|
|
24
25
|
|
|
25
|
-
##
|
|
26
|
+
## Data Sources
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
Improvement information is based on:
|
|
29
|
+
|
|
30
|
+
- Akashi-List
|
|
31
|
+
- wikiwiki.jp
|
|
32
|
+
- KcWiki-Api
|
|
33
|
+
- KC3
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
The plugin uses the latest available data and shows the active data version and update time in **Help**.
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Install `poi-plugin-remodel-assistant` from the Poi plugin manager.
|
|
32
40
|
|
|
33
41
|
## License
|
|
34
42
|
|
package/data/changelog.json
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
{
|
|
5
5
|
"version": "1.2.3",
|
|
6
6
|
"items": [
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"changelog_1_2_3_runtime_data_auto_update_fix",
|
|
8
|
+
"changelog_1_2_3_equipment_planning_ui",
|
|
9
|
+
"changelog_1_2_3_problem_diagnostics",
|
|
10
|
+
"changelog_1_2_3_host_compatibility_and_stability"
|
|
9
11
|
]
|
|
10
12
|
},
|
|
11
13
|
{
|
package/i18n/en-US.json
CHANGED
|
@@ -1465,7 +1465,6 @@
|
|
|
1465
1465
|
"app.changelog_1_2_0_backup": "Back up and restore plans, route choices, interface settings, and related user data.",
|
|
1466
1466
|
"app.changelog_1_2_0_help_and_data": "Illustrated Help is included with the active data version and update time, and interface/help language follows Poi i18n settings.",
|
|
1467
1467
|
"app.Data package updated, refreshing decision chain...": "Data package updated. Refreshing decision chain…",
|
|
1468
|
-
"app.changelog_1_2_3_latent_issue_fixes": "Fixed several latent issues.",
|
|
1469
1468
|
"app.changelog_1_2_3_runtime_data_auto_update_fix": "Fixed an issue that could prevent the data package from updating automatically as expected.",
|
|
1470
1469
|
"app.Diagnostics": "Diagnostics",
|
|
1471
1470
|
"diagnostics.All known plugin-owned Poi config keys, including compatibility keys": "All known plugin-owned Poi config keys, including compatibility keys",
|
|
@@ -1503,5 +1502,12 @@
|
|
|
1503
1502
|
"diagnostics.Canonical plugin backup with plans, route choices, assignments, blacklist, and UI preferences": "Canonical plugin backup with plans, route choices, assignments, blacklist, and UI preferences",
|
|
1504
1503
|
"equipment.Upgrade chain terminal": "Upgrade endpoint",
|
|
1505
1504
|
"equipment.Improvement route": "Improvement route",
|
|
1506
|
-
"equipment.Improvement endpoint": "Improvement endpoint"
|
|
1505
|
+
"equipment.Improvement endpoint": "Improvement endpoint",
|
|
1506
|
+
"app.changelog_1_2_3_equipment_planning_ui": "Improved plan and owned-equipment information in Today and the Equipment Library, refined upgrade-route filters, and now remembers search and sort choices.",
|
|
1507
|
+
"app.changelog_1_2_3_problem_diagnostics": "Added problem diagnostics: clear caches and reload the plugin first, then export diagnostic data if the problem remains.",
|
|
1508
|
+
"app.changelog_1_2_3_host_compatibility_and_stability": "Improved Poi 12 / React 18 compatibility and fixed several latent issues.",
|
|
1509
|
+
"diagnostics.Exports plugin diagnostic data and the personal inventory data stored by Poi under Local Storage/leveldb.": "Packages plugin diagnostic data and the personal inventory state stored by Poi under Local Storage/leveldb.",
|
|
1510
|
+
"diagnostics.Plugin diagnostic data file": "Plugin diagnostic data",
|
|
1511
|
+
"diagnostics.Poi personal inventory data file": "Poi personal inventory data",
|
|
1512
|
+
"diagnostics.Some Poi personal inventory files were in use and could not be copied. The skipped files are listed in manifest.json.": "Some Poi personal inventory files were in use and could not be copied. The diagnostic bundle was saved and the skipped files are listed in manifest.json."
|
|
1507
1513
|
}
|
package/i18n/ja-JP.json
CHANGED
|
@@ -1465,7 +1465,6 @@
|
|
|
1465
1465
|
"app.changelog_1_2_0_backup": "計画、ルート選択、画面設定などのデータをバックアップ/復元できます。",
|
|
1466
1466
|
"app.changelog_1_2_0_help_and_data": "図解ヘルプを内蔵し、現在のデータ版と更新時刻を表示します。画面とヘルプの言語は Poi の i18n 設定に連動します。",
|
|
1467
1467
|
"app.Data package updated, refreshing decision chain...": "データパッケージを更新しました。判断チェーンを更新しています…",
|
|
1468
|
-
"app.changelog_1_2_3_latent_issue_fixes": "いくつかの潜在的な問題を修正しました。",
|
|
1469
1468
|
"app.changelog_1_2_3_runtime_data_auto_update_fix": "データパッケージが想定どおり自動更新されないことがある問題を修正しました。",
|
|
1470
1469
|
"app.Diagnostics": "診断",
|
|
1471
1470
|
"diagnostics.All known plugin-owned Poi config keys, including compatibility keys": "互換キーを含む、このプラグインの既知の Poi 設定一式",
|
|
@@ -1503,5 +1502,12 @@
|
|
|
1503
1502
|
"diagnostics.Canonical plugin backup with plans, route choices, assignments, blacklist, and UI preferences": "計画、ルート選択、装備割り当て、低優先装備、UI設定を含む標準プラグインバックアップ",
|
|
1504
1503
|
"equipment.Upgrade chain terminal": "改修終点",
|
|
1505
1504
|
"equipment.Improvement route": "改修ルート",
|
|
1506
|
-
"equipment.Improvement endpoint": "改修終点"
|
|
1505
|
+
"equipment.Improvement endpoint": "改修終点",
|
|
1506
|
+
"app.changelog_1_2_3_equipment_planning_ui": "「今日の改修」と装備ライブラリの計画・所持装備情報の表示を改善し、改修ルートのフィルターを整理して、検索・並び順を記憶するようにしました。",
|
|
1507
|
+
"app.changelog_1_2_3_problem_diagnostics": "問題診断を追加しました。まずキャッシュを消去してプラグインを再読み込みし、問題が残る場合は診断データを出力できます。",
|
|
1508
|
+
"app.changelog_1_2_3_host_compatibility_and_stability": "Poi 12 / React 18 との互換性を改善し、いくつかの潜在的な問題を修正しました。",
|
|
1509
|
+
"diagnostics.Exports plugin diagnostic data and the personal inventory data stored by Poi under Local Storage/leveldb.": "プラグインの診断データと、Poi の Local Storage/leveldb にある個人インベントリ状態のデータをまとめます。",
|
|
1510
|
+
"diagnostics.Plugin diagnostic data file": "プラグイン診断データ",
|
|
1511
|
+
"diagnostics.Poi personal inventory data file": "Poi 個人インベントリデータ",
|
|
1512
|
+
"diagnostics.Some Poi personal inventory files were in use and could not be copied. The skipped files are listed in manifest.json.": "一部の Poi 個人インベントリファイルは使用中のためコピーできませんでした。診断 ZIP は保存され、スキップしたファイルは manifest.json に記録されています。"
|
|
1507
1513
|
}
|
package/i18n/zh-CN.json
CHANGED
|
@@ -1465,7 +1465,6 @@
|
|
|
1465
1465
|
"app.changelog_1_2_0_backup": "支持计划、路线选择和界面设置等数据的备份与恢复。",
|
|
1466
1466
|
"app.changelog_1_2_0_help_and_data": "内置图文帮助,并显示当前数据版本和更新时间;界面与帮助语言对接 Poi 的 i18n 设置。",
|
|
1467
1467
|
"app.Data package updated, refreshing decision chain...": "数据包已更新,正在刷新决策链路…",
|
|
1468
|
-
"app.changelog_1_2_3_latent_issue_fixes": "修复一些潜在问题。",
|
|
1469
1468
|
"app.changelog_1_2_3_runtime_data_auto_update_fix": "修复数据包无法按预期自动更新的问题。",
|
|
1470
1469
|
"app.Diagnostics": "诊断",
|
|
1471
1470
|
"diagnostics.All known plugin-owned Poi config keys, including compatibility keys": "本插件全部已知 Poi 配置项(含兼容键)",
|
|
@@ -1503,5 +1502,12 @@
|
|
|
1503
1502
|
"diagnostics.Canonical plugin backup with plans, route choices, assignments, blacklist, and UI preferences": "插件标准备份,包含计划、路线选择、装备分配、低优先装备和界面设置",
|
|
1504
1503
|
"equipment.Upgrade chain terminal": "改修终点",
|
|
1505
1504
|
"equipment.Improvement route": "改修路线",
|
|
1506
|
-
"equipment.Improvement endpoint": "改修终点"
|
|
1505
|
+
"equipment.Improvement endpoint": "改修终点",
|
|
1506
|
+
"app.changelog_1_2_3_equipment_planning_ui": "优化“今天改什么”和装备库的计划与持有装备信息显示,改进改修路线筛选,并记住搜索与排序。",
|
|
1507
|
+
"app.changelog_1_2_3_problem_diagnostics": "新增问题诊断,可先清理缓存并重载插件;问题仍存在时可导出诊断数据。",
|
|
1508
|
+
"app.changelog_1_2_3_host_compatibility_and_stability": "提升 Poi 12 / React 18 兼容性并修复若干潜在问题。",
|
|
1509
|
+
"diagnostics.Exports plugin diagnostic data and the personal inventory data stored by Poi under Local Storage/leveldb.": "打包插件诊断数据,以及 Poi 的 Local Storage/leveldb 中用于恢复个人库存现场的数据。",
|
|
1510
|
+
"diagnostics.Plugin diagnostic data file": "插件诊断数据",
|
|
1511
|
+
"diagnostics.Poi personal inventory data file": "Poi 个人库存数据",
|
|
1512
|
+
"diagnostics.Some Poi personal inventory files were in use and could not be copied. The skipped files are listed in manifest.json.": "部分 Poi 个人库存文件正在被占用,未能复制;诊断包已保存,跳过的文件记录在 manifest.json 中。"
|
|
1507
1513
|
}
|
package/i18n/zh-TW.json
CHANGED
|
@@ -1465,7 +1465,6 @@
|
|
|
1465
1465
|
"app.changelog_1_2_0_backup": "支援計畫、路線選擇與介面設定等資料的備份與還原。",
|
|
1466
1466
|
"app.changelog_1_2_0_help_and_data": "內建圖文幫助,並顯示目前資料版本與更新時間;介面與幫助語言對接 Poi 的 i18n 設定。",
|
|
1467
1467
|
"app.Data package updated, refreshing decision chain...": "資料包已更新,正在重新整理決策鏈路…",
|
|
1468
|
-
"app.changelog_1_2_3_latent_issue_fixes": "修復一些潛在問題。",
|
|
1469
1468
|
"app.changelog_1_2_3_runtime_data_auto_update_fix": "修復資料包無法按預期自動更新的問題。",
|
|
1470
1469
|
"app.Diagnostics": "診斷",
|
|
1471
1470
|
"diagnostics.All known plugin-owned Poi config keys, including compatibility keys": "此外掛全部已知 Poi 設定項(含相容鍵)",
|
|
@@ -1503,5 +1502,12 @@
|
|
|
1503
1502
|
"diagnostics.Canonical plugin backup with plans, route choices, assignments, blacklist, and UI preferences": "外掛標準備份,包含計畫、路線選擇、裝備分配、低優先裝備與介面設定",
|
|
1504
1503
|
"equipment.Upgrade chain terminal": "改修終點",
|
|
1505
1504
|
"equipment.Improvement route": "改修路線",
|
|
1506
|
-
"equipment.Improvement endpoint": "改修終點"
|
|
1505
|
+
"equipment.Improvement endpoint": "改修終點",
|
|
1506
|
+
"app.changelog_1_2_3_equipment_planning_ui": "最佳化「今天改什麼」與裝備庫的計畫及持有裝備資訊顯示,改進改修路線篩選,並記住搜尋與排序。",
|
|
1507
|
+
"app.changelog_1_2_3_problem_diagnostics": "新增問題診斷,可先清理快取並重新載入外掛;問題仍存在時可匯出診斷資料。",
|
|
1508
|
+
"app.changelog_1_2_3_host_compatibility_and_stability": "提升 Poi 12 / React 18 相容性並修復若干潛在問題。",
|
|
1509
|
+
"diagnostics.Exports plugin diagnostic data and the personal inventory data stored by Poi under Local Storage/leveldb.": "打包外掛診斷資料,以及 Poi 的 Local Storage/leveldb 中用於還原個人庫存現場的資料。",
|
|
1510
|
+
"diagnostics.Plugin diagnostic data file": "外掛診斷資料",
|
|
1511
|
+
"diagnostics.Poi personal inventory data file": "Poi 個人庫存資料",
|
|
1512
|
+
"diagnostics.Some Poi personal inventory files were in use and could not be copied. The skipped files are listed in manifest.json.": "部分 Poi 個人庫存檔案正在被佔用,未能複製;診斷包已儲存,略過的檔案記錄在 manifest.json 中。"
|
|
1507
1513
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poi-plugin-remodel-assistant",
|
|
3
|
-
"version": "1.2.3
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "View enhanced KanColle improvement data, manage improvement plans, and receive improvement recommendations.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"icon": "fa/wrench",
|
|
28
28
|
"id": "poi-plugin-remodel-assistant",
|
|
29
29
|
"priority": 50,
|
|
30
|
-
"releaseChannel": "
|
|
30
|
+
"releaseChannel": "stable",
|
|
31
31
|
"title": "改修助手",
|
|
32
32
|
"runtimeFlags": {
|
|
33
33
|
"forceFullDecisionRebuild": false
|
|
@@ -428,12 +428,12 @@ class NewInterfaceArea extends _react.Component {
|
|
|
428
428
|
bsSize: "small",
|
|
429
429
|
onClick: this.handleShowDataTransfer
|
|
430
430
|
}, __('Data')), _react.default.createElement(_reactBootstrap.Button, {
|
|
431
|
-
bsSize: "small",
|
|
432
|
-
onClick: this.handleShowProblemDiagnostics
|
|
433
|
-
}, __('Diagnostics')), _react.default.createElement(_reactBootstrap.Button, {
|
|
434
431
|
bsSize: "small",
|
|
435
432
|
onClick: onShowHelp
|
|
436
|
-
}, __('Help'))
|
|
433
|
+
}, __('Help')), _react.default.createElement(_reactBootstrap.Button, {
|
|
434
|
+
bsSize: "small",
|
|
435
|
+
onClick: this.handleShowProblemDiagnostics
|
|
436
|
+
}, __('Diagnostics')))), runtimeDataDecisionRefreshPending && _react.default.createElement(_reactBootstrap.Alert, {
|
|
437
437
|
bsStyle: "info",
|
|
438
438
|
className: "decision-runtime-data-refresh-note"
|
|
439
439
|
}, __('Data package updated, refreshing decision chain...')), _react.default.createElement("main", {
|
|
@@ -33,10 +33,6 @@ const {
|
|
|
33
33
|
|
|
34
34
|
const __ = (key, ...args) => translate(`diagnostics.${key}`, ...args);
|
|
35
35
|
|
|
36
|
-
const {
|
|
37
|
-
config
|
|
38
|
-
} = window;
|
|
39
|
-
|
|
40
36
|
const downloadBinary = (filename, buffer) => {
|
|
41
37
|
const blob = new Blob([buffer], {
|
|
42
38
|
type: 'application/zip'
|
|
@@ -65,6 +61,7 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
65
61
|
busy: false,
|
|
66
62
|
confirmingReset: false,
|
|
67
63
|
message: '',
|
|
64
|
+
warning: '',
|
|
68
65
|
error: ''
|
|
69
66
|
}
|
|
70
67
|
}), Object.defineProperty(this, "refreshPreview", {
|
|
@@ -72,18 +69,12 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
72
69
|
enumerable: true,
|
|
73
70
|
writable: true,
|
|
74
71
|
value: () => {
|
|
75
|
-
const {
|
|
76
|
-
inventory
|
|
77
|
-
} = this.props;
|
|
78
72
|
this.setState({
|
|
79
|
-
bundleFiles: (0, _problemDiagnostics.previewDiagnosticBundleContents)(
|
|
80
|
-
config,
|
|
81
|
-
inventory,
|
|
82
|
-
hostWindow: window
|
|
83
|
-
}),
|
|
73
|
+
bundleFiles: (0, _problemDiagnostics.previewDiagnosticBundleContents)(),
|
|
84
74
|
busy: false,
|
|
85
75
|
confirmingReset: false,
|
|
86
76
|
message: '',
|
|
77
|
+
warning: '',
|
|
87
78
|
error: ''
|
|
88
79
|
});
|
|
89
80
|
}
|
|
@@ -100,14 +91,12 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
100
91
|
enumerable: true,
|
|
101
92
|
writable: true,
|
|
102
93
|
value: async event => {
|
|
103
|
-
const {
|
|
104
|
-
inventory
|
|
105
|
-
} = this.props;
|
|
106
94
|
const hostWindow = event.currentTarget.ownerDocument.defaultView;
|
|
107
95
|
const filename = (0, _problemDiagnostics.makeDiagnosticBundleFilename)();
|
|
108
96
|
this.setState({
|
|
109
97
|
busy: true,
|
|
110
98
|
message: '',
|
|
99
|
+
warning: '',
|
|
111
100
|
error: ''
|
|
112
101
|
});
|
|
113
102
|
let selection;
|
|
@@ -130,18 +119,15 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
130
119
|
}
|
|
131
120
|
|
|
132
121
|
try {
|
|
133
|
-
const bundle = (0, _problemDiagnostics.buildDiagnosticBundle)(
|
|
134
|
-
config,
|
|
135
|
-
inventory,
|
|
136
|
-
hostWindow
|
|
137
|
-
});
|
|
122
|
+
const bundle = await (0, _problemDiagnostics.buildDiagnosticBundle)();
|
|
138
123
|
|
|
139
124
|
if (!selection.supported) {
|
|
140
125
|
downloadBinary(filename, bundle.buffer);
|
|
141
126
|
this.setState({
|
|
142
127
|
busy: false,
|
|
143
128
|
bundleFiles: bundle.files,
|
|
144
|
-
message: __('Diagnostic bundle exported')
|
|
129
|
+
message: __('Diagnostic bundle exported'),
|
|
130
|
+
warning: bundle.partial ? __('Some Poi personal inventory files were in use and could not be copied. The skipped files are listed in manifest.json.') : ''
|
|
145
131
|
});
|
|
146
132
|
return;
|
|
147
133
|
}
|
|
@@ -151,7 +137,8 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
151
137
|
this.setState({
|
|
152
138
|
busy: false,
|
|
153
139
|
bundleFiles: bundle.files,
|
|
154
|
-
message: __('Diagnostic bundle saved as %s').replace('%s', _path.default.basename(selection.filePath))
|
|
140
|
+
message: __('Diagnostic bundle saved as %s').replace('%s', _path.default.basename(selection.filePath)),
|
|
141
|
+
warning: bundle.partial ? __('Some Poi personal inventory files were in use and could not be copied. The skipped files are listed in manifest.json.') : ''
|
|
155
142
|
});
|
|
156
143
|
} catch (error) {
|
|
157
144
|
this.setState({
|
|
@@ -168,6 +155,7 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
168
155
|
this.setState({
|
|
169
156
|
confirmingReset: true,
|
|
170
157
|
message: '',
|
|
158
|
+
warning: '',
|
|
171
159
|
error: ''
|
|
172
160
|
});
|
|
173
161
|
}
|
|
@@ -189,6 +177,7 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
189
177
|
this.setState({
|
|
190
178
|
busy: true,
|
|
191
179
|
message: '',
|
|
180
|
+
warning: '',
|
|
192
181
|
error: ''
|
|
193
182
|
});
|
|
194
183
|
|
|
@@ -236,7 +225,8 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
236
225
|
busy,
|
|
237
226
|
confirmingReset,
|
|
238
227
|
error,
|
|
239
|
-
message
|
|
228
|
+
message,
|
|
229
|
+
warning
|
|
240
230
|
} = this.state;
|
|
241
231
|
return _react.default.createElement(_poiDialog.PoiDialog, {
|
|
242
232
|
show: show,
|
|
@@ -254,21 +244,9 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
254
244
|
bsStyle: "danger"
|
|
255
245
|
}, error), message && _react.default.createElement(_reactBootstrap.Alert, {
|
|
256
246
|
bsStyle: "success"
|
|
257
|
-
}, message), _react.default.createElement(
|
|
258
|
-
|
|
259
|
-
}, _react.default.createElement("
|
|
260
|
-
className: "decision-diagnostics-section-copy"
|
|
261
|
-
}, _react.default.createElement("h4", null, __('Export diagnostic bundle')), _react.default.createElement("p", null, __('Exports the inventory and all plugin configuration needed to reproduce a problem.')), _react.default.createElement("p", {
|
|
262
|
-
className: "decision-diagnostics-warning"
|
|
263
|
-
}, __('The bundle contains inventory and plugin settings. Review it before sharing.')), _react.default.createElement("strong", null, __('Files included')), this.renderFileList()), _react.default.createElement(_reactBootstrap.Button, {
|
|
264
|
-
className: "decision-diagnostics-primary-action",
|
|
265
|
-
bsStyle: "primary",
|
|
266
|
-
disabled: busy,
|
|
267
|
-
onClick: this.handleExport
|
|
268
|
-
}, _react.default.createElement("span", {
|
|
269
|
-
className: "glyphicon glyphicon-save",
|
|
270
|
-
"aria-hidden": "true"
|
|
271
|
-
}), __('Choose save location'))), _react.default.createElement("section", {
|
|
247
|
+
}, message), warning && _react.default.createElement(_reactBootstrap.Alert, {
|
|
248
|
+
bsStyle: "warning"
|
|
249
|
+
}, warning), _react.default.createElement("section", {
|
|
272
250
|
className: "decision-diagnostics-section is-danger"
|
|
273
251
|
}, _react.default.createElement("div", {
|
|
274
252
|
className: "decision-diagnostics-section-copy"
|
|
@@ -288,7 +266,19 @@ class ProblemDiagnosticsModal extends _react.Component {
|
|
|
288
266
|
bsStyle: "danger",
|
|
289
267
|
disabled: busy,
|
|
290
268
|
onClick: this.handleConfirmReset
|
|
291
|
-
}, busy ? __('Clearing caches...') : __('Confirm clear and restart'))))
|
|
269
|
+
}, busy ? __('Clearing caches...') : __('Confirm clear and restart')))), _react.default.createElement("section", {
|
|
270
|
+
className: "decision-diagnostics-section"
|
|
271
|
+
}, _react.default.createElement("div", {
|
|
272
|
+
className: "decision-diagnostics-section-copy"
|
|
273
|
+
}, _react.default.createElement("h4", null, __('Export diagnostic bundle')), _react.default.createElement("p", null, __('Exports plugin diagnostic data and the personal inventory data stored by Poi under Local Storage/leveldb.')), _react.default.createElement("strong", null, __('Files included')), this.renderFileList()), _react.default.createElement(_reactBootstrap.Button, {
|
|
274
|
+
className: "decision-diagnostics-primary-action",
|
|
275
|
+
bsStyle: "primary",
|
|
276
|
+
disabled: busy,
|
|
277
|
+
onClick: this.handleExport
|
|
278
|
+
}, _react.default.createElement("span", {
|
|
279
|
+
className: "glyphicon glyphicon-save",
|
|
280
|
+
"aria-hidden": "true"
|
|
281
|
+
}), __('Choose save location')))), _react.default.createElement(_poiDialog.PoiDialog.Footer, null, _react.default.createElement(_reactBootstrap.Button, {
|
|
292
282
|
disabled: busy,
|
|
293
283
|
onClick: this.handleClose
|
|
294
284
|
}, __('Close'))), _react.default.createElement(_modalResizeHandle.ModalResizeHandle, {
|
|
@@ -5,6 +5,8 @@ exports.showZipSaveDialog = exports.showJsonSaveDialog = exports.showJsonOpenDia
|
|
|
5
5
|
|
|
6
6
|
var _fs = _interopRequireDefault(require("fs"));
|
|
7
7
|
|
|
8
|
+
var _os = _interopRequireDefault(require("os"));
|
|
9
|
+
|
|
8
10
|
var _path = _interopRequireDefault(require("path"));
|
|
9
11
|
|
|
10
12
|
var _personalProfileStorage = require("./personal-profile-storage");
|
|
@@ -42,6 +44,18 @@ const ensurePluginDataDirectory = () => {
|
|
|
42
44
|
return directory;
|
|
43
45
|
};
|
|
44
46
|
|
|
47
|
+
const diagnosticExportDirectory = remote => {
|
|
48
|
+
try {
|
|
49
|
+
if (remote && remote.app && typeof remote.app.getPath === 'function') {
|
|
50
|
+
const downloads = remote.app.getPath('downloads');
|
|
51
|
+
if (downloads) return downloads;
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {// Fall back to the user's Downloads directory when host path lookup is unavailable.
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return _path.default.join(_os.default.homedir(), 'Downloads');
|
|
57
|
+
};
|
|
58
|
+
|
|
45
59
|
const focusRenderer = (hostWindow, remote) => {
|
|
46
60
|
try {
|
|
47
61
|
if (hostWindow && typeof hostWindow.focus === 'function') hostWindow.focus();
|
|
@@ -168,7 +182,7 @@ const showZipSaveDialog = async (hostWindow, filename, title = 'Export diagnosti
|
|
|
168
182
|
try {
|
|
169
183
|
const result = await runDialog(remote.dialog, 'showSaveDialog', dialogParent(remote), {
|
|
170
184
|
title,
|
|
171
|
-
defaultPath: _path.default.join(
|
|
185
|
+
defaultPath: _path.default.join(diagnosticExportDirectory(remote), filename),
|
|
172
186
|
filters: [{
|
|
173
187
|
name: 'ZIP',
|
|
174
188
|
extensions: ['zip']
|
|
@@ -11,8 +11,6 @@ var _os = _interopRequireDefault(require("os"));
|
|
|
11
11
|
|
|
12
12
|
var _path = _interopRequireDefault(require("path"));
|
|
13
13
|
|
|
14
|
-
var _changelog = require("../app/changelog");
|
|
15
|
-
|
|
16
14
|
var _decisionFoundationService = require("../new-interface/app/decision-foundation-service");
|
|
17
15
|
|
|
18
16
|
var _decisionSnapshotCache = require("../new-interface/app/decision-snapshot-cache");
|
|
@@ -25,10 +23,6 @@ var _acquisitionDomain = require("../new-interface/equipment/domain/acquisition-
|
|
|
25
23
|
|
|
26
24
|
var _inventoryDomain = require("../new-interface/equipment/domain/inventory-domain");
|
|
27
25
|
|
|
28
|
-
var _currentStartSelectionDomain = require("../new-interface/plan/domain/current-start-selection-domain");
|
|
29
|
-
|
|
30
|
-
var _currentStartAssignmentDomain = require("../new-interface/plan/domain/current-start-assignment-domain");
|
|
31
|
-
|
|
32
26
|
var _dataPackageDomain = require("./domain/data-package-domain");
|
|
33
27
|
|
|
34
28
|
var _improvementCostCache = require("./improvement-cost-cache");
|
|
@@ -37,26 +31,18 @@ var _personalProfileStorage = require("./personal-profile-storage");
|
|
|
37
31
|
|
|
38
32
|
var _pluginMeta = require("./plugin-meta");
|
|
39
33
|
|
|
40
|
-
var _planStorage = require("./plan-storage");
|
|
41
|
-
|
|
42
34
|
var _runtimeDataPackage = require("./runtime-data-package");
|
|
43
35
|
|
|
44
|
-
var _dataTransfer = require("./data-transfer");
|
|
45
|
-
|
|
46
|
-
var _uiPreferences = require("./ui-preferences");
|
|
47
|
-
|
|
48
|
-
var _uiZoom = require("./ui-zoom");
|
|
49
|
-
|
|
50
|
-
var _windowBounds = require("./window-bounds");
|
|
51
|
-
|
|
52
36
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
53
37
|
|
|
54
38
|
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
55
39
|
|
|
56
40
|
const DIAGNOSTIC_BUNDLE_FORMAT = `${_pluginMeta.PLUGIN_ID}.diagnostic-bundle`;
|
|
57
41
|
exports.DIAGNOSTIC_BUNDLE_FORMAT = DIAGNOSTIC_BUNDLE_FORMAT;
|
|
58
|
-
const DIAGNOSTIC_BUNDLE_SCHEMA_VERSION =
|
|
42
|
+
const DIAGNOSTIC_BUNDLE_SCHEMA_VERSION = 3;
|
|
59
43
|
exports.DIAGNOSTIC_BUNDLE_SCHEMA_VERSION = DIAGNOSTIC_BUNDLE_SCHEMA_VERSION;
|
|
44
|
+
const DIAGNOSTIC_TEMP_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
45
|
+
const DIAGNOSTIC_COPY_RETRY_DELAYS_MS = [0, 40, 120];
|
|
60
46
|
|
|
61
47
|
const json = value => `${JSON.stringify(value, null, 2)}\n`;
|
|
62
48
|
|
|
@@ -70,237 +56,116 @@ const portablePath = value => {
|
|
|
70
56
|
return home && value.startsWith(home) ? `~${value.slice(home.length)}` : value;
|
|
71
57
|
};
|
|
72
58
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
59
|
+
const toArchivePath = value => value.split(_path.default.sep).join('/');
|
|
60
|
+
|
|
61
|
+
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
62
|
+
|
|
63
|
+
const diagnosticSourceSpecs = () => {
|
|
64
|
+
const pluginDataDirectory = (0, _personalProfileStorage.getPluginDataDirectory)();
|
|
65
|
+
const runtimeDataPaths = (0, _runtimeDataPackage.getRuntimeDataPackagePaths)();
|
|
66
|
+
const pluginArchiveRoot = `poi/plugin-data/${_pluginMeta.PLUGIN_ID}`;
|
|
67
|
+
return [{
|
|
68
|
+
kind: 'directory',
|
|
69
|
+
sourcePath: _path.default.join((0, _personalProfileStorage.resolvePoiAppDataPath)(), 'Local Storage', 'leveldb'),
|
|
70
|
+
archiveRoot: 'poi/Local Storage/leveldb',
|
|
71
|
+
description: 'Poi personal inventory data file',
|
|
72
|
+
bestEffort: true
|
|
73
|
+
}, {
|
|
74
|
+
kind: 'file',
|
|
75
|
+
sourcePath: _path.default.join(pluginDataDirectory, 'personal-profile.json'),
|
|
76
|
+
archivePath: `${pluginArchiveRoot}/personal-profile.json`,
|
|
77
|
+
description: 'Plugin diagnostic data file',
|
|
78
|
+
bestEffort: false
|
|
79
|
+
}, {
|
|
80
|
+
kind: 'directory',
|
|
81
|
+
sourcePath: runtimeDataPaths.current,
|
|
82
|
+
archiveRoot: `${pluginArchiveRoot}/runtime-data-package/current`,
|
|
83
|
+
description: 'Plugin diagnostic data file',
|
|
84
|
+
bestEffort: false
|
|
85
|
+
}, {
|
|
86
|
+
kind: 'file',
|
|
87
|
+
sourcePath: runtimeDataPaths.status,
|
|
88
|
+
archivePath: `${pluginArchiveRoot}/runtime-data-package/status.json`,
|
|
89
|
+
description: 'Plugin diagnostic data file',
|
|
90
|
+
bestEffort: false
|
|
91
|
+
}];
|
|
99
92
|
};
|
|
100
93
|
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const pluginConfigKeys = Array.from(new Set([...currentConfigKeys, ..._planStorage.LEGACY_PLAN_STORAGE_KEYS, ..._currentStartSelectionDomain.LEGACY_CURRENT_START_SELECTION_KEYS, ..._pluginMeta.LEGACY_PLUGIN_IDS.flatMap(pluginId => legacySuffixes.map(suffix => `${pluginId}.${suffix}`))])).sort();
|
|
104
|
-
|
|
105
|
-
const readConfigSubset = (config, keys) => {
|
|
106
|
-
if (!config || typeof config.get !== 'function') return {};
|
|
107
|
-
const missing = {};
|
|
108
|
-
return keys.reduce((result, key) => {
|
|
109
|
-
const value = config.get(key, missing);
|
|
110
|
-
if (value !== missing) result[key] = value;
|
|
111
|
-
return result;
|
|
112
|
-
}, {});
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const readPluginLocalStorage = hostWindow => {
|
|
116
|
-
const storage = hostWindow && hostWindow.localStorage;
|
|
117
|
-
if (!storage) return {};
|
|
118
|
-
|
|
119
|
-
const prefixes = _pluginMeta.COMPATIBLE_PLUGIN_IDS.map(pluginId => `${pluginId}.`);
|
|
120
|
-
|
|
121
|
-
const result = {};
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
for (let index = 0; index < storage.length; index += 1) {
|
|
125
|
-
const key = storage.key(index);
|
|
126
|
-
if (!key || !prefixes.some(prefix => key.startsWith(prefix))) continue;
|
|
127
|
-
result[key] = storage.getItem(key);
|
|
128
|
-
}
|
|
129
|
-
} catch (error) {
|
|
130
|
-
return {
|
|
131
|
-
__error: error && error.message ? error.message : String(error)
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return result;
|
|
136
|
-
};
|
|
94
|
+
const enumerateDirectoryFiles = spec => {
|
|
95
|
+
const entries = [];
|
|
137
96
|
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
const statusBuffer = safeRead(paths.status);
|
|
141
|
-
let status = null;
|
|
97
|
+
const visit = (directory, relativeDirectory = '') => {
|
|
98
|
+
let names;
|
|
142
99
|
|
|
143
|
-
if (statusBuffer) {
|
|
144
100
|
try {
|
|
145
|
-
|
|
101
|
+
names = _fs.default.readdirSync(directory).sort();
|
|
146
102
|
} catch (error) {
|
|
147
|
-
|
|
148
|
-
invalidJson: true,
|
|
149
|
-
raw: statusBuffer.toString('utf8')
|
|
150
|
-
};
|
|
103
|
+
return;
|
|
151
104
|
}
|
|
152
|
-
}
|
|
153
105
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
106
|
+
names.forEach(name => {
|
|
107
|
+
const sourcePath = _path.default.join(directory, name);
|
|
108
|
+
|
|
109
|
+
let stat;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
stat = _fs.default.lstatSync(sourcePath);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (stat.isSymbolicLink()) return;
|
|
118
|
+
const relativePath = relativeDirectory ? _path.default.join(relativeDirectory, name) : name;
|
|
119
|
+
|
|
120
|
+
if (stat.isDirectory()) {
|
|
121
|
+
visit(sourcePath, relativePath);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!stat.isFile()) return;
|
|
126
|
+
entries.push({
|
|
127
|
+
sourcePath,
|
|
128
|
+
archivePath: `${spec.archiveRoot}/${toArchivePath(relativePath)}`,
|
|
129
|
+
description: spec.description,
|
|
130
|
+
bestEffort: spec.bestEffort,
|
|
131
|
+
bytes: stat.size
|
|
132
|
+
});
|
|
133
|
+
});
|
|
159
134
|
};
|
|
160
|
-
};
|
|
161
135
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return Object.keys(paths).reduce((result, key) => _extends({}, result, {
|
|
165
|
-
[key]: statSummary(paths[key])
|
|
166
|
-
}), {});
|
|
136
|
+
if (_fs.default.existsSync(spec.sourcePath)) visit(spec.sourcePath);
|
|
137
|
+
return entries;
|
|
167
138
|
};
|
|
168
139
|
|
|
169
|
-
const
|
|
170
|
-
|
|
140
|
+
const enumerateSourceFiles = (specs = diagnosticSourceSpecs()) => specs.reduce((entries, spec) => {
|
|
141
|
+
if (spec.kind === 'directory') return entries.concat(enumerateDirectoryFiles(spec));
|
|
142
|
+
if (!_fs.default.existsSync(spec.sourcePath)) return entries;
|
|
143
|
+
let stat;
|
|
171
144
|
|
|
172
145
|
try {
|
|
173
|
-
|
|
174
|
-
dataPackage = {
|
|
175
|
-
identity: (0, _dataPackageDomain.getDataPackageIdentity)(),
|
|
176
|
-
packageVersion: snapshot && snapshot.packageVersion,
|
|
177
|
-
root: portablePath(snapshot && snapshot.root),
|
|
178
|
-
manifest: snapshot && snapshot.manifest
|
|
179
|
-
};
|
|
146
|
+
stat = _fs.default.lstatSync(spec.sourcePath);
|
|
180
147
|
} catch (error) {
|
|
181
|
-
|
|
182
|
-
error: error && error.message ? error.message : String(error)
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
plugin: {
|
|
188
|
-
id: _pluginMeta.PLUGIN_ID,
|
|
189
|
-
version: _pluginMeta.PLUGIN_VERSION,
|
|
190
|
-
releaseChannel: _pluginMeta.RELEASE_CHANNEL
|
|
191
|
-
},
|
|
192
|
-
runtime: {
|
|
193
|
-
platform: process.platform,
|
|
194
|
-
arch: process.arch,
|
|
195
|
-
versions: process.versions
|
|
196
|
-
},
|
|
197
|
-
poi: {
|
|
198
|
-
appDataPath: portablePath((0, _personalProfileStorage.resolvePoiAppDataPath)()),
|
|
199
|
-
pluginDataPath: portablePath((0, _personalProfileStorage.getPluginDataDirectory)())
|
|
200
|
-
},
|
|
201
|
-
inventorySource: inventory && inventory.source,
|
|
202
|
-
dataPackage
|
|
203
|
-
};
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const virtualEntries = ({
|
|
207
|
-
config,
|
|
208
|
-
inventory,
|
|
209
|
-
hostWindow
|
|
210
|
-
}) => [{
|
|
211
|
-
path: 'inventory/equipment.json',
|
|
212
|
-
description: 'Current effective equipment inventory used by the plugin',
|
|
213
|
-
content: json(inventory && inventory.equips || {})
|
|
214
|
-
}, {
|
|
215
|
-
path: 'inventory/ships.json',
|
|
216
|
-
description: 'Current effective ship inventory used by the plugin',
|
|
217
|
-
content: json(inventory && inventory.ships || {})
|
|
218
|
-
}, {
|
|
219
|
-
path: 'inventory/resources.json',
|
|
220
|
-
description: 'Current effective resources used by the plugin',
|
|
221
|
-
content: json(inventory && inventory.resources || [])
|
|
222
|
-
}, {
|
|
223
|
-
path: 'inventory/useitems.json',
|
|
224
|
-
description: 'Current effective useitem inventory used by the plugin',
|
|
225
|
-
content: json(inventory && inventory.useitems || {})
|
|
226
|
-
}, {
|
|
227
|
-
path: 'config/plugin-backup.json',
|
|
228
|
-
description: 'Canonical plugin backup with plans, route choices, assignments, blacklist, and UI preferences',
|
|
229
|
-
content: (0, _dataTransfer.serializeExportPayload)(config)
|
|
230
|
-
}, {
|
|
231
|
-
path: 'config/plugin-config.json',
|
|
232
|
-
description: 'All known plugin-owned Poi config keys, including compatibility keys',
|
|
233
|
-
content: json({
|
|
234
|
-
pluginIds: _pluginMeta.COMPATIBLE_PLUGIN_IDS,
|
|
235
|
-
values: readConfigSubset(config, pluginConfigKeys)
|
|
236
|
-
})
|
|
237
|
-
}, {
|
|
238
|
-
path: 'config/host-config.json',
|
|
239
|
-
description: 'Small Poi host config subset consumed by this plugin',
|
|
240
|
-
content: json(readConfigSubset(config, ['poi.misc.language', 'packageManager.mirrorName', 'poi.window.x', 'poi.window.y']))
|
|
241
|
-
}, {
|
|
242
|
-
path: 'config/plugin-local-storage.json',
|
|
243
|
-
description: 'Plugin-owned renderer localStorage values such as modal bounds',
|
|
244
|
-
content: json(readPluginLocalStorage(hostWindow))
|
|
245
|
-
}, {
|
|
246
|
-
path: 'runtime/environment.json',
|
|
247
|
-
description: 'Plugin/runtime/data-package identity used for reproduction',
|
|
248
|
-
content: json(diagnosticEnvironment(inventory))
|
|
249
|
-
}, {
|
|
250
|
-
path: 'runtime/runtime-data-status.json',
|
|
251
|
-
description: 'Runtime data package status and cache metadata',
|
|
252
|
-
content: json(runtimeDataStatus())
|
|
253
|
-
}, {
|
|
254
|
-
path: 'runtime/improvement-cost-cache-status.json',
|
|
255
|
-
description: 'Improvement-cost cache metadata',
|
|
256
|
-
content: json(improvementCostCacheStatus())
|
|
257
|
-
}];
|
|
258
|
-
|
|
259
|
-
const optionalFileEntries = () => {
|
|
260
|
-
const entries = [];
|
|
261
|
-
const profilePath = (0, _personalProfileStorage.getPersonalProfilePath)();
|
|
262
|
-
const profile = safeRead(profilePath);
|
|
263
|
-
|
|
264
|
-
if (profile) {
|
|
265
|
-
entries.push({
|
|
266
|
-
path: 'inventory/personal-profile.json',
|
|
267
|
-
description: 'Persisted personal profile when present',
|
|
268
|
-
content: profile
|
|
269
|
-
});
|
|
148
|
+
return entries;
|
|
270
149
|
}
|
|
271
150
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
151
|
+
if (!stat.isFile() || stat.isSymbolicLink()) return entries;
|
|
152
|
+
return entries.concat({
|
|
153
|
+
sourcePath: spec.sourcePath,
|
|
154
|
+
archivePath: spec.archivePath,
|
|
155
|
+
description: spec.description,
|
|
156
|
+
bestEffort: spec.bestEffort,
|
|
157
|
+
bytes: stat.size
|
|
280
158
|
});
|
|
281
|
-
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const normalizeEntries = entries => entries.map(entry => _extends({}, entry, {
|
|
285
|
-
content: Buffer.isBuffer(entry.content) ? entry.content : Buffer.from(String(entry.content), 'utf8')
|
|
286
|
-
}));
|
|
159
|
+
}, []);
|
|
287
160
|
|
|
288
|
-
const previewDiagnosticBundleContents = ({
|
|
289
|
-
config,
|
|
290
|
-
inventory,
|
|
291
|
-
hostWindow
|
|
292
|
-
} = {}) => [{
|
|
161
|
+
const previewDiagnosticBundleContents = () => [{
|
|
293
162
|
path: 'manifest.json',
|
|
294
163
|
description: 'Diagnostic bundle manifest',
|
|
295
164
|
bytes: null
|
|
296
|
-
}, ...
|
|
297
|
-
|
|
298
|
-
inventory,
|
|
299
|
-
hostWindow
|
|
300
|
-
}), ...optionalFileEntries()]).map(entry => ({
|
|
301
|
-
path: entry.path,
|
|
165
|
+
}, ...enumerateSourceFiles().map(entry => ({
|
|
166
|
+
path: entry.archivePath,
|
|
302
167
|
description: entry.description,
|
|
303
|
-
bytes: entry.
|
|
168
|
+
bytes: entry.bytes
|
|
304
169
|
}))];
|
|
305
170
|
|
|
306
171
|
exports.previewDiagnosticBundleContents = previewDiagnosticBundleContents;
|
|
@@ -395,43 +260,193 @@ const zipBuffer = entries => {
|
|
|
395
260
|
return Buffer.concat([...localParts, centralDirectory, end]);
|
|
396
261
|
};
|
|
397
262
|
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
263
|
+
const diagnosticTempRoot = () => _path.default.join((0, _personalProfileStorage.getPluginDataDirectory)(), 'temp', 'diagnostics');
|
|
264
|
+
|
|
265
|
+
const cleanupStaleDiagnosticTemp = () => {
|
|
266
|
+
const root = diagnosticTempRoot();
|
|
267
|
+
if (!_fs.default.existsSync(root)) return;
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
let names;
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
names = _fs.default.readdirSync(root);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
names.forEach(name => {
|
|
278
|
+
const target = _path.default.join(root, name);
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const stat = _fs.default.lstatSync(target);
|
|
282
|
+
|
|
283
|
+
if (stat.isDirectory() && now - stat.mtimeMs > DIAGNOSTIC_TEMP_MAX_AGE_MS) {
|
|
284
|
+
_fs.default.rmSync(target, {
|
|
285
|
+
recursive: true,
|
|
286
|
+
force: true
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {// Stale temp cleanup is best effort and must never block diagnostics.
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const createDiagnosticStage = () => {
|
|
295
|
+
cleanupStaleDiagnosticTemp();
|
|
296
|
+
const root = diagnosticTempRoot();
|
|
297
|
+
|
|
298
|
+
_fs.default.mkdirSync(root, {
|
|
299
|
+
recursive: true
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const session = `${Date.now()}-${_crypto.default.randomBytes(6).toString('hex')}`;
|
|
303
|
+
|
|
304
|
+
const stage = _path.default.join(root, session);
|
|
305
|
+
|
|
306
|
+
_fs.default.mkdirSync(stage, {
|
|
307
|
+
recursive: true
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return stage;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const copyFileWithRetry = async entry => {
|
|
314
|
+
let lastError = null;
|
|
315
|
+
|
|
316
|
+
for (let attempt = 0; attempt < DIAGNOSTIC_COPY_RETRY_DELAYS_MS.length; attempt += 1) {
|
|
317
|
+
const delay = DIAGNOSTIC_COPY_RETRY_DELAYS_MS[attempt];
|
|
318
|
+
if (delay > 0) await wait(delay);
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
await _fs.default.promises.copyFile(entry.sourcePath, entry.stagePath);
|
|
322
|
+
return {
|
|
323
|
+
copied: true,
|
|
324
|
+
attempts: attempt + 1
|
|
325
|
+
};
|
|
326
|
+
} catch (error) {
|
|
327
|
+
lastError = error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
copied: false,
|
|
333
|
+
attempts: DIAGNOSTIC_COPY_RETRY_DELAYS_MS.length,
|
|
334
|
+
error: lastError
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const snapshotDiagnosticSources = async stage => {
|
|
339
|
+
const sources = diagnosticSourceSpecs();
|
|
340
|
+
const candidates = enumerateSourceFiles(sources);
|
|
341
|
+
const copied = [];
|
|
342
|
+
const failures = [];
|
|
343
|
+
|
|
344
|
+
for (const entry of candidates) {
|
|
345
|
+
const stagePath = _path.default.join(stage, ...entry.archivePath.split('/'));
|
|
346
|
+
|
|
347
|
+
_fs.default.mkdirSync(_path.default.dirname(stagePath), {
|
|
348
|
+
recursive: true
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const result = await copyFileWithRetry(_extends({}, entry, {
|
|
352
|
+
stagePath
|
|
353
|
+
}));
|
|
354
|
+
|
|
355
|
+
if (result.copied) {
|
|
356
|
+
copied.push(_extends({}, entry, {
|
|
357
|
+
stagePath,
|
|
358
|
+
attempts: result.attempts
|
|
359
|
+
}));
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const failure = {
|
|
364
|
+
path: entry.archivePath,
|
|
365
|
+
sourcePath: portablePath(entry.sourcePath),
|
|
366
|
+
errorCode: result.error && result.error.code ? result.error.code : 'UNKNOWN',
|
|
367
|
+
attempts: result.attempts
|
|
368
|
+
};
|
|
369
|
+
failures.push(failure);
|
|
370
|
+
|
|
371
|
+
if (!entry.bestEffort) {
|
|
372
|
+
const error = new Error('plugin-diagnostic-snapshot-copy-failed');
|
|
373
|
+
error.cause = result.error;
|
|
374
|
+
error.diagnosticFailure = failure;
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
sources,
|
|
381
|
+
copied,
|
|
382
|
+
failures
|
|
421
383
|
};
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const stagedEntries = copied => copied.map(entry => {
|
|
387
|
+
const content = _fs.default.readFileSync(entry.stagePath);
|
|
388
|
+
|
|
427
389
|
return {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}))
|
|
390
|
+
path: entry.archivePath,
|
|
391
|
+
description: entry.description,
|
|
392
|
+
sourcePath: entry.sourcePath,
|
|
393
|
+
content,
|
|
394
|
+
attempts: entry.attempts
|
|
434
395
|
};
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const buildDiagnosticBundle = async () => {
|
|
399
|
+
const stage = createDiagnosticStage();
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
const snapshot = await snapshotDiagnosticSources(stage);
|
|
403
|
+
const baseEntries = stagedEntries(snapshot.copied);
|
|
404
|
+
const manifest = {
|
|
405
|
+
format: DIAGNOSTIC_BUNDLE_FORMAT,
|
|
406
|
+
schemaVersion: DIAGNOSTIC_BUNDLE_SCHEMA_VERSION,
|
|
407
|
+
generatedAt: new Date().toISOString(),
|
|
408
|
+
pluginVersion: _pluginMeta.PLUGIN_VERSION,
|
|
409
|
+
releaseChannel: _pluginMeta.RELEASE_CHANNEL,
|
|
410
|
+
snapshotStatus: snapshot.failures.length > 0 ? 'partial' : 'complete',
|
|
411
|
+
notice: 'Contains whitelisted plugin diagnostic data and a best-effort Poi Local Storage snapshot used to reproduce personal inventory state.',
|
|
412
|
+
sources: snapshot.sources.map(source => ({
|
|
413
|
+
kind: source.kind,
|
|
414
|
+
sourcePath: portablePath(source.sourcePath),
|
|
415
|
+
archiveRoot: source.archiveRoot || null,
|
|
416
|
+
archivePath: source.archivePath || null,
|
|
417
|
+
exists: _fs.default.existsSync(source.sourcePath),
|
|
418
|
+
bestEffort: source.bestEffort
|
|
419
|
+
})),
|
|
420
|
+
files: baseEntries.map(entry => ({
|
|
421
|
+
path: entry.path,
|
|
422
|
+
description: entry.description,
|
|
423
|
+
sourcePath: portablePath(entry.sourcePath),
|
|
424
|
+
bytes: entry.content.length,
|
|
425
|
+
sha256: sha256(entry.content),
|
|
426
|
+
copyAttempts: entry.attempts
|
|
427
|
+
})),
|
|
428
|
+
failures: snapshot.failures
|
|
429
|
+
};
|
|
430
|
+
const entries = [{
|
|
431
|
+
path: 'manifest.json',
|
|
432
|
+
description: 'Diagnostic bundle manifest',
|
|
433
|
+
content: Buffer.from(json(manifest), 'utf8')
|
|
434
|
+
}, ...baseEntries];
|
|
435
|
+
return {
|
|
436
|
+
buffer: zipBuffer(entries),
|
|
437
|
+
partial: snapshot.failures.length > 0,
|
|
438
|
+
files: entries.map(entry => ({
|
|
439
|
+
path: entry.path,
|
|
440
|
+
description: entry.description,
|
|
441
|
+
bytes: entry.content.length
|
|
442
|
+
}))
|
|
443
|
+
};
|
|
444
|
+
} finally {
|
|
445
|
+
_fs.default.rmSync(stage, {
|
|
446
|
+
recursive: true,
|
|
447
|
+
force: true
|
|
448
|
+
});
|
|
449
|
+
}
|
|
435
450
|
};
|
|
436
451
|
|
|
437
452
|
exports.buildDiagnosticBundle = buildDiagnosticBundle;
|