chore(jcef): 更新缓存日志文件的时间戳和进程ID

- 更新了多个JCEF缓存目录下的LOG文件时间戳
- 更新了chrome_debug.log中的时间戳和进程ID
- 更新了Extension State、GCM Store、Local Storage等目录的LOG文件
- 保持了原有的缓存数据结构和功能
- 修正了Session Storage目录中的时间戳信息
- 更新了shared_proto_db和Site Characteristics Database的元数据日志
This commit is contained in:
2026-01-02 17:49:28 +08:00
parent 8de2b0f2fe
commit 752850b936
37 changed files with 732 additions and 464 deletions

View File

@@ -1,3 +1,3 @@
#Current Loaded Language
#Fri Jan 02 17:08:53 CST 2026
#Fri Jan 02 17:46:22 CST 2026
loadedLanguage=system\:zh_CN

Binary file not shown.

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.309 d58 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/MANIFEST-000001
2025/12/28-14:50:54.309 d58 Recovering log #3
2025/12/28-14:50:54.312 d58 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/000003.log
2026/01/02-17:46:55.167 52c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/MANIFEST-000001
2026/01/02-17:46:55.168 52c Recovering log #3
2026/01/02-17:46:55.169 52c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.868 bcc Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/MANIFEST-000001
2025/12/28-14:19:49.869 bcc Recovering log #3
2025/12/28-14:19:49.881 bcc Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/000003.log
2025/12/28-14:50:54.309 d58 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/MANIFEST-000001
2025/12/28-14:50:54.309 d58 Recovering log #3
2025/12/28-14:50:54.312 d58 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:57.787 24fc Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/MANIFEST-000001
2025/12/28-14:50:57.787 24fc Recovering log #3
2025/12/28-14:50:57.788 24fc Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/000003.log
2026/01/02-17:46:58.729 3d98 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/MANIFEST-000001
2026/01/02-17:46:58.730 3d98 Recovering log #3
2026/01/02-17:46:58.730 3d98 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:54.454 4760 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/MANIFEST-000001
2025/12/28-14:19:54.457 4760 Recovering log #3
2025/12/28-14:19:54.458 4760 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/000003.log
2025/12/28-14:50:57.787 24fc Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/MANIFEST-000001
2025/12/28-14:50:57.787 24fc Recovering log #3
2025/12/28-14:50:57.788 24fc Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/000003.log

Binary file not shown.

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.386 2b54 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/MANIFEST-000001
2025/12/28-14:50:54.399 2b54 Recovering log #44
2025/12/28-14:50:54.404 2b54 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/000044.log
2026/01/02-17:46:55.667 31c0 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/MANIFEST-000001
2026/01/02-17:46:55.683 31c0 Recovering log #44
2026/01/02-17:46:55.687 31c0 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/000044.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.965 2d90 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/MANIFEST-000001
2025/12/28-14:19:49.981 2d90 Recovering log #44
2025/12/28-14:19:49.986 2d90 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/000044.log
2025/12/28-14:50:54.386 2b54 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/MANIFEST-000001
2025/12/28-14:50:54.399 2b54 Recovering log #44
2025/12/28-14:50:54.404 2b54 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/000044.log

View File

@@ -1 +1 @@
{"net":{"http_server_properties":{"broken_alternative_services":[{"anonymization":["JAAAAB0AAABodHRwczovL3VwZGF0ZS5nb29nbGVhcGlzLmNvbQAAAA==",false],"broken_count":10,"broken_until":"1767056398","host":"update.googleapis.com","port":443,"protocol_str":"quic"},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"broken_count":1,"broken_until":"1766904958","host":"fonts.googleapis.com","port":443,"protocol_str":"quic"}],"servers":[{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://code.jquery.com","supports_spdy":true},{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13413970258167864","port":443,"protocol_str":"quic"}],"anonymization":["JAAAAB0AAABodHRwczovL3VwZGF0ZS5nb29nbGVhcGlzLmNvbQAAAA==",false],"server":"https://update.googleapis.com","supports_spdy":true},{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13413970255009851","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://fonts.googleapis.com","supports_spdy":true},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://cdn.jsdelivr.net","supports_spdy":true}],"version":5},"network_qualities":{"CAESABiAgICA+P////8B":"4G"}}}
{"net":{"http_server_properties":{"broken_alternative_services":[{"anonymization":["JAAAAB0AAABodHRwczovL3VwZGF0ZS5nb29nbGVhcGlzLmNvbQAAAA==",false],"broken_count":10,"host":"update.googleapis.com","port":443,"protocol_str":"quic"},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"broken_count":1,"host":"fonts.googleapis.com","port":443,"protocol_str":"quic"}],"servers":[{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://code.jquery.com","supports_spdy":true},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://fonts.googleapis.com","supports_spdy":true},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://cdn.jsdelivr.net","supports_spdy":true},{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13414412819106787","port":443,"protocol_str":"quic"}],"anonymization":["JAAAAB0AAABodHRwczovL3VwZGF0ZS5nb29nbGVhcGlzLmNvbQAAAA==",false],"server":"https://update.googleapis.com","supports_spdy":true}],"version":5},"network_qualities":{"CAESABiAgICA+P////8B":"4G"}}}

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
2025/12/28-14:51:01.393 2160 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/MANIFEST-000001
2025/12/28-14:51:01.395 2160 Recovering log #12
2025/12/28-14:51:01.401 2160 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/000012.log
2026/01/02-17:48:16.445 31c0 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/MANIFEST-000001
2026/01/02-17:48:16.446 31c0 Recovering log #12
2026/01/02-17:48:16.449 31c0 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/000012.log

View File

@@ -1,3 +1,3 @@
2025/12/20-18:28:04.218 294 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/MANIFEST-000001
2025/12/20-18:28:04.219 294 Recovering log #12
2025/12/20-18:28:04.222 294 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/000012.log
2025/12/28-14:51:01.393 2160 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/MANIFEST-000001
2025/12/28-14:51:01.395 2160 Recovering log #12
2025/12/28-14:51:01.401 2160 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/000012.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.257 186c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/MANIFEST-000001
2025/12/28-14:50:54.263 186c Recovering log #3
2025/12/28-14:50:54.264 186c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/000003.log
2026/01/02-17:46:55.084 3b7c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/MANIFEST-000001
2026/01/02-17:46:55.114 3b7c Recovering log #3
2026/01/02-17:46:55.115 3b7c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.782 193c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/MANIFEST-000001
2025/12/28-14:19:49.813 193c Recovering log #3
2025/12/28-14:19:49.814 193c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/000003.log
2025/12/28-14:50:54.257 186c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/MANIFEST-000001
2025/12/28-14:50:54.263 186c Recovering log #3
2025/12/28-14:50:54.264 186c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.253 37d4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/MANIFEST-000001
2025/12/28-14:50:54.263 37d4 Recovering log #3
2025/12/28-14:50:54.264 37d4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/000003.log
2026/01/02-17:46:55.071 52c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/MANIFEST-000001
2026/01/02-17:46:55.115 52c Recovering log #3
2026/01/02-17:46:55.115 52c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.766 bcc Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/MANIFEST-000001
2025/12/28-14:19:49.813 bcc Recovering log #3
2025/12/28-14:19:49.814 bcc Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/000003.log
2025/12/28-14:50:54.253 37d4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/MANIFEST-000001
2025/12/28-14:50:54.263 37d4 Recovering log #3
2025/12/28-14:50:54.264 37d4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.299 22b4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/MANIFEST-000001
2025/12/28-14:50:54.299 22b4 Recovering log #19
2025/12/28-14:50:54.301 22b4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/000019.log
2026/01/02-17:46:55.164 109c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/MANIFEST-000001
2026/01/02-17:46:55.164 109c Recovering log #19
2026/01/02-17:46:55.169 109c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/000019.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.860 6ac Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/MANIFEST-000001
2025/12/28-14:19:49.861 6ac Recovering log #19
2025/12/28-14:19:49.866 6ac Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/000019.log
2025/12/28-14:50:54.299 22b4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/MANIFEST-000001
2025/12/28-14:50:54.299 22b4 Recovering log #19
2025/12/28-14:50:54.301 22b4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/000019.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.290 22b4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/MANIFEST-000001
2025/12/28-14:50:54.291 22b4 Recovering log #3
2025/12/28-14:50:54.291 22b4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/000003.log
2026/01/02-17:46:55.156 109c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/MANIFEST-000001
2026/01/02-17:46:55.156 109c Recovering log #3
2026/01/02-17:46:55.157 109c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.851 6ac Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/MANIFEST-000001
2025/12/28-14:19:49.852 6ac Recovering log #3
2025/12/28-14:19:49.854 6ac Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/000003.log
2025/12/28-14:50:54.290 22b4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/MANIFEST-000001
2025/12/28-14:50:54.291 22b4 Recovering log #3
2025/12/28-14:50:54.291 22b4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/000003.log

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"user_experience_metrics.stability.exited_cleanly":false,"variations_crash_streak":594}
{"user_experience_metrics.stability.exited_cleanly":false,"variations_crash_streak":595}

View File

@@ -1,6 +1,4 @@
[12284:16860:1228/145054.244:WARNING:account_consistency_mode_manager.cc(77)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[12284:16860:1228/145054.280:WARNING:extension_service.cc(2065)] Found external version of extension ncennffkjdiamlpmcbajkmaiiiddgioothat is older than current version. Current version is: 3.52.14. New version is: 3.52.5. Keeping current version.
[8880:16484:1228/145054.563:WARNING:viz_main_impl.cc(85)] VizNullHypothesis is disabled (not a warning)
[13676:18520:1228/145254.442:WARNING:viz_main_impl.cc(85)] VizNullHypothesis is disabled (not a warning)
[14320:9004:1228/145354.607:ERROR:gpu_blocklist.cc(71)] Unable to get gpu adapter
[12284:16860:1228/145354.607:ERROR:service_client.cc(36)] Unexpected on_device_model service disconnect: The device's GPU is not supported.
[4944:21460:0102/174655.052:WARNING:account_consistency_mode_manager.cc(77)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[4944:21460:0102/174655.144:WARNING:extension_service.cc(2065)] Found external version of extension ncennffkjdiamlpmcbajkmaiiiddgioothat is older than current version. Current version is: 3.52.14. New version is: 3.52.5. Keeping current version.
[5764:9508:0102/174655.913:WARNING:viz_main_impl.cc(85)] VizNullHypothesis is disabled (not a warning)
[5060:21232:0102/174855.188:WARNING:viz_main_impl.cc(85)] VizNullHypothesis is disabled (not a warning)

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#FileLock
#Sun Dec 28 14:52:28 CST 2025
#Fri Jan 02 17:46:53 CST 2026
hostName=192.168.116.1
id=19b63bad12a8483d76995f9c5100715a8b474ee84cc
id=19b7e1a4b3ed8755d1cb7723021bb5b3ae83bb80d46
method=file
server=192.168.116.1\:64976
server=192.168.116.1\:64658

View File

@@ -839,7 +839,7 @@ public class AxisInnovatorsBox {
UIManager.getSystemLookAndFeelClassName(),
LanguageManager.getLoadedLanguages().getText("default_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("default_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:default_theme",
isDarkMode
);
@@ -849,7 +849,7 @@ public class AxisInnovatorsBox {
"javax.swing.plaf.metal.MetalLookAndFeel",
LanguageManager.getLoadedLanguages().getText("metal_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("metal_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:metal_theme",
false
);
@@ -859,7 +859,7 @@ public class AxisInnovatorsBox {
"com.sun.java.swing.plaf.motif.MotifLookAndFeel",
LanguageManager.getLoadedLanguages().getText("motif_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("motif_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:motif_theme",
false
);
@@ -870,7 +870,7 @@ public class AxisInnovatorsBox {
new com.formdev.flatlaf.FlatLightLaf(),
LanguageManager.getLoadedLanguages().getText("flatLight_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatLight_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatLight_theme",
false
);
@@ -880,7 +880,7 @@ public class AxisInnovatorsBox {
new com.formdev.flatlaf.FlatDarkLaf(),
LanguageManager.getLoadedLanguages().getText("flatDark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatDark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatDark_theme",
true
);
@@ -890,7 +890,7 @@ public class AxisInnovatorsBox {
new com.formdev.flatlaf.FlatIntelliJLaf(),
LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatIntelliJ_theme",
false
);
@@ -900,7 +900,7 @@ public class AxisInnovatorsBox {
new com.formdev.flatlaf.FlatDarculaLaf(),
LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatDarcula_theme",
true
);
@@ -910,7 +910,7 @@ public class AxisInnovatorsBox {
new FlatMacLightLaf(),
LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatMacLight_theme",
false
);
@@ -920,7 +920,7 @@ public class AxisInnovatorsBox {
new FlatMacDarkLaf(),
LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatMacDark_theme",
true
);
@@ -929,7 +929,7 @@ public class AxisInnovatorsBox {
new MaterialLookAndFeel(new JMarsDarkTheme()),
LanguageManager.getLoadedLanguages().getText("mars_dark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("mars_dark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:mars_dark_theme",
true
);
@@ -938,7 +938,7 @@ public class AxisInnovatorsBox {
new MaterialLookAndFeel(new MaterialLiteTheme()),
LanguageManager.getLoadedLanguages().getText("material_lite_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("material_lite_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:material_lite_theme",
false
);
@@ -947,7 +947,7 @@ public class AxisInnovatorsBox {
new MaterialLookAndFeel(new MaterialOceanicTheme()),
LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:material_oceanic_theme",
true
);

View File

@@ -1,31 +1,35 @@
package com.axis.innovators.box.plugins;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 自定义类加载器装载CorePlugins
* @author tzdwindows 7
* 优化:增加缓冲读取,优化集合初始化
*/
public class BoxClassLoader extends URLClassLoader {
private static final List<IClassTransformer> CLASS_TRANSFORMS = new CopyOnWriteArrayList<>();
private static final List<String> CLASS_BLACKLIST = new ArrayList<>();
private static final List<String> CLASS_LOADING_LIST = Collections.synchronizedList(new ArrayList<>());
// 使用 CopyOnWriteArrayList 适合读多写少的场景
private static final List<String> CLASS_BLACKLIST = new CopyOnWriteArrayList<>();
// Loading list 仅用于防止循环依赖,普通同步 List 即可
private static final List<String> CLASS_LOADING_LIST = Collections.synchronizedList(new java.util.ArrayList<>(50));
private static final List<Class<?>> CLASS_LOADING_LIST_OBJECT = new CopyOnWriteArrayList<>();
static {
Collections.addAll(CLASS_BLACKLIST,
// 批量添加,减少扩容开销
List<String> blacklist = java.util.Arrays.asList(
"java.", "javax.", "sun.", "com.sun.", "jdk.",
"org.xml.", "org.w3c.", "org.apache.",
"javax.management.", "javax.swing."
, "javafx.","org.jnativehook.","com.dustinredmond."
"javax.management.", "javax.swing.",
"javafx.", "org.jnativehook.", "com.dustinredmond."
);
CLASS_BLACKLIST.addAll(blacklist);
}
public BoxClassLoader(ClassLoader parent) {
@@ -60,7 +64,7 @@ public class BoxClassLoader extends URLClassLoader {
resolveClass(c);
}
return c;
} catch (ClassNotFoundException e) {
} catch (ClassNotFoundException | SecurityException e) {
return super.loadClass(name, resolve);
}
}
@@ -68,7 +72,8 @@ public class BoxClassLoader extends URLClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 移除 synchronized (getClassLoadingLock(name)),因为 loadClass 已经锁住了
// 防止循环依赖
if (CLASS_LOADING_LIST.contains(name)) {
throw new ClassCircularityError(name + " circular loading detected");
}
@@ -76,32 +81,39 @@ public class BoxClassLoader extends URLClassLoader {
CLASS_LOADING_LIST.add(name);
try {
byte[] clazzByte = getClassBytes(name);
// 只有当存在转换器时才遍历
if (!CLASS_TRANSFORMS.isEmpty()) {
for (IClassTransformer transformer : CLASS_TRANSFORMS) {
byte[] transformed = transformer.transform(name, transformer.getClass().getName(), clazzByte);
if (transformed != null) clazzByte = transformed;
}
}
Class<?> clazz = defineClass(name, clazzByte, 0, clazzByte.length);
CLASS_LOADING_LIST_OBJECT.add(clazz);
return clazz;
} catch (IOException e) {
throw new ClassNotFoundException("Class byte loading failed", e);
throw new ClassNotFoundException("Class byte loading failed: " + name, e);
} finally {
CLASS_LOADING_LIST.remove(name);
}
}
}
private byte[] getClassBytes(String className) throws IOException, ClassNotFoundException {
String path = className.replace('.', '/') + ".class";
try (InputStream is = getResourceAsStream(path)) {
if (is == null) throw new ClassNotFoundException(className);
return is.readAllBytes();
try (BufferedInputStream bis = new BufferedInputStream(is)) {
return bis.readAllBytes();
}
}
}
private boolean isBlacklisted(String className) {
return CLASS_BLACKLIST.stream().anyMatch(className::startsWith);
for (String s : CLASS_BLACKLIST) {
if (className.startsWith(s)) return true;
}
return false;
}
public static void addClassTransformer(IClassTransformer transformer) {

View File

@@ -1,8 +1,10 @@
package com.axis.innovators.box.plugins;
import java.net.URL;
import java.util.List;
/**
* 插件描述符
* @author tzdwindows 7
*/
public class PluginDescriptor {
@@ -15,21 +17,93 @@ public class PluginDescriptor {
private String transformers;
private String registrationName;
/**
* 核心字段:插件的类加载器
* 保存这个是为了以后调用 classLoader.getResource("images/logo.png")
* 从而准确地从该插件的 JAR 包中获取资源,互不干扰。
*/
private ClassLoader classLoader;
/**
* 核心字段:插件 JAR 包的物理 URL
* 格式通常为: file:/C:/path/to/plugin.jar
* 用于构建 jar:file: 协议的 URL (常用于 JavaFX Image 或 FXML 加载)
*/
private URL sourceLocation;
// Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<String> getSupportedVersions() { return supportedVersions; }
public void setSupportedVersions(List<String> supportedVersions) { this.supportedVersions = supportedVersions; }
public String getIcon() { return icon; }
public void setIcon(String icon) { this.icon = icon; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Object getInstance() { return instance; }
public void setInstance(Object instance) { this.instance = instance; }
public String getTransformers() { return transformers; }
public void setTransformers(String transformers) { this.transformers = transformers; }
public String getRegistrationName() { return registrationName; }
public void setRegistrationName(String registrationName) { this.registrationName = registrationName; }
/**
* 获取用于加载此插件资源的 ClassLoader
*/
public ClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* 获取插件 JAR 文件的物理路径 URL
*/
public URL getSourceLocation() {
return sourceLocation;
}
public void setSourceLocation(URL sourceLocation) {
this.sourceLocation = sourceLocation;
}
// --- 辅助方法:便捷加载资源 ---
/**
* 这是一个便捷方法,用于直接从当前插件的 Resources 中获取资源 URL。
* 哪怕多个插件有同名文件(如 config.png使用此方法也只会加载当前插件内的那个。
*
* @param path 资源路径,例如 "assets/icon.png" (不需要以 / 开头)
* @return 资源的完整 URL如果找不到则返回 null
*/
public URL getResource(String path) {
if (classLoader != null) {
return classLoader.getResource(path);
}
return null;
}
/**
* 获取资源的输入流(用于读取配置文件等)
* @param path 资源路径
* @return 输入流
*/
public java.io.InputStream getResourceAsStream(String path) {
if (classLoader != null) {
return classLoader.getResourceAsStream(path);
}
return null;
}
}

View File

@@ -6,96 +6,185 @@ import com.axis.innovators.box.tools.FolderCreator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* 插件加载器
* @author tzdwindows 7
* 插件加载器 - 性能优化版 (修复 SystemClassLoader 启动崩溃问题)
*/
public class PluginLoader {
private static Logger logger;
// 移除 static 初始化,防止在 SystemClassLoader 构造期间触发 Log4j 初始化
private static volatile Logger logger;
public static final String PLUGIN_PATH = FolderCreator.getPluginFolder();
private static final List<PluginDescriptor> loadedPlugins = new ArrayList<>();
private static final List<IClassTransformer> transformers = new ArrayList<>();
private static final List<String> corePluginMainClass = new ArrayList<>();
private static final List<String> pluginRegisteredName = new ArrayList<>();
public static void loadPlugins() throws IOException, PluginLoadingError {
private static final List<PluginDescriptor> loadedPlugins = new CopyOnWriteArrayList<>();
private static final List<IClassTransformer> transformers = new CopyOnWriteArrayList<>();
private static final List<String> corePluginMainClass = new CopyOnWriteArrayList<>();
private static final Set<String> pluginRegisteredName = Collections.synchronizedSet(new HashSet<>());
// =========================================================
// 安全日志相关方法 (Safe Logger)
// =========================================================
/**
* 获取 Logger如果 Log4j 尚未准备好(例如在 SystemClassLoader 初始化期间),则返回 null
*/
private static Logger getLogger() {
if (logger != null) return logger;
try {
// 尝试初始化 Log4j
logger = LogManager.getLogger(PluginLoader.class);
File pluginDir = null;
if (PLUGIN_PATH != null) {
pluginDir = new File(PLUGIN_PATH);
return logger;
} catch (Throwable t) {
// 捕获 BootstrapMethodError 或 IllegalStateException
// 说明此时 SystemClassLoader 正在构造中,不能使用 Log4j
return null;
}
File[] jars = null;
if (pluginDir != null) {
jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
}
if (jars == null) {
return;
private static void logInfo(String msg, Object... args) {
Logger l = getLogger();
if (l != null) {
l.info(msg, args);
} else {
// Fallback: 在 Log4j 准备好之前使用 System.out
System.out.println("[PluginLoader-INFO] " + formatFallback(msg, args));
}
for (int i = 0; i < jars.length; i++) {
processJarFile(jars[i],false);
}
private static void logError(String msg, Object... args) {
Logger l = getLogger();
if (l != null) {
l.error(msg, args);
} else {
System.err.println("[PluginLoader-ERROR] " + formatFallback(msg, args));
// 打印异常堆栈
for (Object arg : args) {
if (arg instanceof Throwable) {
((Throwable) arg).printStackTrace();
}
}
}
}
private static void logWarn(String msg, Object... args) {
Logger l = getLogger();
if (l != null) {
l.warn(msg, args);
} else {
System.out.println("[PluginLoader-WARN] " + formatFallback(msg, args));
}
}
private static void logDebug(String msg, Object... args) {
Logger l = getLogger();
if (l != null) {
l.debug(msg, args);
} else {
// Debug 级别在 fallback 模式下通常不打印,或者可以打印到 System.out
// System.out.println("[PluginLoader-DEBUG] " + formatFallback(msg, args));
}
}
// 简单的字符串格式化,用于 fallback 模式
private static String formatFallback(String msg, Object... args) {
if (args == null || args.length == 0) return msg;
try {
// 简单替换 {},不完美但够用
String result = msg;
for (Object arg : args) {
if (arg instanceof Throwable) continue; // 异常单独处理
result = result.replaceFirst("\\{\\}", String.valueOf(arg));
}
return result;
} catch (Exception e) {
return msg;
}
}
// =========================================================
// 业务逻辑
// =========================================================
public static void loadPlugins() throws IOException, PluginLoadingError {
File pluginDir = (PLUGIN_PATH != null) ? new File(PLUGIN_PATH) : null;
if (pluginDir == null || !pluginDir.exists()) return;
File[] jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
if (jars == null || jars.length == 0) return;
logInfo("Found {} jars, starting parallel loading...", jars.length);
AtomicInteger loadedCount = new AtomicInteger(0);
int total = jars.length;
Arrays.stream(jars).parallel().forEach(jarFile -> {
try {
processJarFile(jarFile, false);
} catch (Exception e) {
logError("Failed to load plugin jar: " + jarFile.getName(), e);
} finally {
int current = loadedCount.incrementAndGet();
if (AxisInnovatorsBox.getMain() != null && AxisInnovatorsBox.getMain().progressBarManager != null) {
try {
AxisInnovatorsBox.getMain().progressBarManager.updateSubProgress(
"Loading Plugin " + i,
i,
jars.length);
"Loading Plugin " + current, current, total);
} catch (Exception ignored) {}
}
}
});
}
private static void processJarFile(File jarFile, boolean isCorePlugin) throws IOException, PluginLoadingError {
private static void processJarFile(File jarFile, boolean isCorePlugin) throws IOException {
try (JarFile jar = new JarFile(jarFile)) {
// Check for CorePlugin in MANIFEST.MF
if (isCorePlugin) {
Attributes attributes = jar.getManifest().getMainAttributes();
String corePluginClass = attributes.getValue("CorePlugin");
if (corePluginClass != null) {
processCorePlugin(jarFile, corePluginClass);
}
} else {
return;
}
JarEntry pluginFile = jar.getJarEntry("plug-in.box");
PluginDescriptor descriptor = null;
if (pluginFile != null) {
processWithManifest(jar, pluginFile, jarFile);
descriptor = processWithManifest(jar, pluginFile, jarFile);
} else {
processWithAnnotations(jar, jarFile);
descriptor = processWithAnnotations(jar, jarFile);
}
if (descriptor != null) {
loadPluginLanguagesInternal(jar, descriptor);
Attributes attributes = jar.getManifest().getMainAttributes();
String corePluginClass = attributes.getValue("CorePlugin");
descriptor.setTransformers(corePluginClass);
}
} catch (PluginLoadingError e) {
logError("Plugin logic error in {}: {}", jarFile.getName(), e.getMessage());
}
}
/**
* 加载核心插件
* @throws IOException 插件加载失败
*/
public static void loadCorePlugin() throws IOException {
File pluginDir = null;
if (PLUGIN_PATH != null) {
pluginDir = new File(PLUGIN_PATH);
}
File[] jars = null;
if (pluginDir != null) {
jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
}
if (jars == null) {
return;
}
File pluginDir = (PLUGIN_PATH != null) ? new File(PLUGIN_PATH) : null;
if (pluginDir == null || !pluginDir.exists()) return;
File[] jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
if (jars == null) return;
for (File jar : jars) {
try {
processJarFile(jar, true);
} catch (PluginLoadingError e) {
throw new RuntimeException(e);
}
}
}
@@ -110,13 +199,14 @@ public class PluginLoader {
registerTransformers(corePlugin, urlClassLoader);
}
} catch (Exception e) {
logger.error("Failed to load core plugin: {}", corePluginClass, e);
logError("Failed to load core plugin: {}", corePluginClass, e);
}
}
private static void registerTransformers(LoadingCorePlugin corePlugin, ClassLoader classLoader) {
//if (corePlugin.getMainClass() != null) {
if (corePlugin.getMainClass() != null) {
corePluginMainClass.add(corePlugin.getMainClass());
}
String[] transformerClasses = corePlugin.getASMTransformerClass();
if (transformerClasses != null) {
for (String transformerClass : transformerClasses) {
@@ -128,106 +218,166 @@ public class PluginLoader {
BoxClassLoader.addClassTransformer(transformer);
}
} catch (Exception e) {
logger.error("Failed to register transformer: {}", transformerClass, e);
logError("Failed to register transformer: {}", transformerClass, e);
}
}
}
//} else {
// logger.error("Failed to load core plugin: {}", corePlugin.getClass().getName());
//}
}
private static void processWithManifest(JarFile jar, JarEntry entry, File jarFile)
private static PluginDescriptor processWithManifest(JarFile jar, JarEntry entry, File jarFile)
throws IOException, PluginLoadingError {
Properties props = new Properties();
try (InputStream is = jar.getInputStream(entry)) {
props.load(is);
} catch (IOException e) {
logger.error("Failed to load plugin from jar: {}", jarFile.getName(), e);
return;
}
PluginDescriptor descriptor = new PluginDescriptor();
URL jarUrl = jarFile.toURI().toURL();
descriptor.setSourceLocation(jarUrl);
URLClassLoader pluginClassLoader = new URLClassLoader(
new URL[]{jarUrl},
PluginLoader.class.getClassLoader()
);
descriptor.setClassLoader(pluginClassLoader);
descriptor.setId(props.getProperty("id"));
descriptor.setName(props.getProperty("name"));
descriptor.setDescription(props.getProperty("description"));
descriptor.setIcon(props.getProperty("icon"));
descriptor.setSupportedVersions(
Arrays.asList(props.getProperty("supportedVersions").split(","))
);
String versions = props.getProperty("supportedVersions");
if (versions != null) {
descriptor.setSupportedVersions(Arrays.asList(versions.split(",")));
}
String registrationName = props.getProperty("registrationName");
verifyRegisteredNameValid(descriptor, registrationName);
logger.info("Loaded plugin: {}", descriptor.getName());
loadMainClass(props.getProperty("mainClass"), jarFile, descriptor);
logInfo("Loaded plugin via manifest: {}", descriptor.getName());
loadMainClass(props.getProperty("mainClass"), descriptor);
Attributes attributes = jar.getManifest().getMainAttributes();
String corePluginClass = attributes.getValue("CorePlugin");
descriptor.setTransformers(corePluginClass);
loadPluginLanguages(jarFile, descriptor);
return descriptor;
}
private static void verifyRegisteredNameValid(PluginDescriptor descriptor, String registrationName) throws PluginLoadingError {
if (registrationName != null && !registrationName.isEmpty()
&& !pluginRegisteredName.contains(registrationName)) {
pluginRegisteredName.add(registrationName);
} else {
if (registrationName == null || registrationName.isEmpty()) {
throw new PluginLoadingError("Invalid registration name");
}
throw new PluginLoadingError("When the \" "
+ descriptor.getName()
+ "\" plugin is loaded, this plugin contains the same registered name as the previously loaded plugin");
if (registrationName == null || registrationName.trim().isEmpty()) {
throw new PluginLoadingError("Invalid registration name for plugin: " + descriptor.getName());
}
synchronized (pluginRegisteredName) {
if (!pluginRegisteredName.add(registrationName)) {
throw new PluginLoadingError("Duplicate registered name '" + registrationName + "' in plugin " + descriptor.getName());
}
}
descriptor.setRegistrationName(registrationName);
}
private static void processWithAnnotations(JarFile jar, File jarFile) throws PluginLoadingError {
URLClassLoader classLoader = createClassLoader(jarFile);
private static PluginDescriptor processWithAnnotations(JarFile jar, File jarFile) throws PluginLoadingError {
PluginDescriptor foundDescriptor = null;
URLClassLoader classLoader = null;
boolean keepClassLoaderOpen = false;
try {
classLoader = new URLClassLoader(
new URL[]{jarFile.toURI().toURL()},
PluginLoader.class.getClassLoader());
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class")) {
if (classLoader != null) {
processClassEntry(entry, jar,classLoader,jarFile);
if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
String className = entry.getName()
.replace("/", ".")
.replace(".class", "");
}
}
}
}
try {
if (className.equals("module-info")) continue;
private static void loadPluginLanguages(File jarFile, PluginDescriptor plugin) {
try (JarFile jar = new JarFile(jarFile)) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().startsWith("lang/") &&
entry.getName().endsWith(".properties")) {
processLanguageFile(jar, entry, plugin);
Class<?> clazz = classLoader.loadClass(className);
PluginMeta meta = clazz.getAnnotation(PluginMeta.class);
if (meta != null) {
PluginDescriptor descriptor = new PluginDescriptor();
descriptor.setSourceLocation(jarFile.toURI().toURL());
descriptor.setClassLoader(classLoader);
keepClassLoaderOpen = true;
descriptor.setId(meta.id());
descriptor.setName(meta.name());
descriptor.setDescription(meta.description());
descriptor.setIcon(meta.icon());
descriptor.setSupportedVersions(Arrays.asList(meta.supportedVersions()));
verifyRegisteredNameValid(descriptor, meta.registeredName());
Object instance = clazz.getDeclaredConstructor().newInstance();
descriptor.setInstance(instance);
injectInstanceField(clazz, instance, descriptor);
loadedPlugins.add(descriptor);
logInfo("Loaded plugin via annotation: {}", descriptor.getName());
foundDescriptor = descriptor;
break;
}
} catch (Throwable e) {
// 忽略
}
}
}
} catch (IOException e) {
logger.error("Failed to load language files for plugin: {}", plugin.getName(), e);
logError("Error scanning jar for annotations: " + jarFile.getName(), e);
} finally {
if (!keepClassLoaderOpen && classLoader != null) {
try {
classLoader.close();
} catch (IOException e) {
logWarn("Failed to close unused classloader", e);
}
}
}
return foundDescriptor;
}
private static void loadPluginLanguagesInternal(JarFile jar, PluginDescriptor plugin) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.startsWith("lang/") && entryName.endsWith(".properties")) {
processLanguageFile(jar, entry, plugin);
}
else if (entryName.startsWith("assets/lang/") && entryName.endsWith(".properties")) {
processAssetsLanguageFile(plugin, entryName);
}
}
}
private static void processAssetsLanguageFile(PluginDescriptor plugin, String entryName) {
String fileName = entryName.substring(entryName.lastIndexOf("/") + 1);
String langCode = fileName.replace(".properties", "");
String targetRegisteredName = "system:" + langCode;
LanguageManager.registerPluginLanguage(plugin, entryName, targetRegisteredName);
}
private static void processLanguageFile(JarFile jar, JarEntry entry, PluginDescriptor plugin) {
String fileName = entry.getName().substring(entry.getName().lastIndexOf("/") + 1);
String[] parts = fileName.split("[_.]");
if (parts.length < 3) {
return;
}
if (parts.length < 3) return;
String langCode = parts[parts.length - 1].replace(".properties", "");
String langName = plugin.getName() + " " + langCode.toUpperCase();
String registeredName = plugin.getRegistrationName() + "_" + langCode;
try (InputStream is = jar.getInputStream(entry)) {
Properties properties2 = new Properties();
properties2.load(new InputStreamReader(is, StandardCharsets.UTF_8));
LanguageManager.Language language = new LanguageManager.Language(
langName,
registeredName,
null
langName, registeredName, null
) {
@Override
public void loadLanguageFile(String languageFile) {
@@ -235,97 +385,42 @@ public class PluginLoader {
}
};
LanguageManager.addLanguage(language);
logger.info("Loaded plugin language: {} ({})", langName, registeredName);
logDebug("Loaded plugin language: {} ({})", langName, registeredName);
} catch (IOException e) {
logger.error("Failed to load language file: {}", entry.getName(), e);
logError("Failed to load language file: {}", entry.getName(), e);
}
}
private static URLClassLoader createClassLoader(File jarFile) {
try {
return new URLClassLoader(
new URL[]{jarFile.toURI().toURL()},
PluginLoader.class.getClassLoader()
);
} catch (MalformedURLException e) {
logger.error("Error creating URLClassLoader", e);
}
return null;
}
private static void processClassEntry(JarEntry entry,JarFile jar,
URLClassLoader classLoader,
File jarFile) throws PluginLoadingError {
String className = entry.getName()
.replace("/", ".")
.replace(".class", "");
try {
Class<?> clazz = classLoader.loadClass(className);
PluginMeta meta = clazz.getAnnotation(PluginMeta.class);
if (meta != null) {
PluginDescriptor descriptor = new PluginDescriptor();
descriptor.setId(meta.id());
descriptor.setName(meta.name());
descriptor.setDescription(meta.description());
descriptor.setIcon(meta.icon());
descriptor.setSupportedVersions(Arrays.asList(meta.supportedVersions()));
String registrationName = meta.registeredName();
verifyRegisteredNameValid(descriptor, registrationName);
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
descriptor.setInstance(instance);
try {
Field pluginInstance = instance.getClass().getDeclaredField("INSTANCE");
pluginInstance.setAccessible(true);
pluginInstance.set(null, descriptor);
} catch (NoSuchFieldException | IllegalArgumentException
| SecurityException | IllegalAccessException e) {
logger.warn("Failed to set plugin instance: {}", instance.getClass(), e);
}
loadedPlugins.add(descriptor);
Attributes attributes = jar.getManifest().getMainAttributes();
String corePluginClass = attributes.getValue("CorePlugin");
descriptor.setTransformers(corePluginClass);
loadPluginLanguages(jarFile, descriptor);
logger.info("Loaded plugin: {}", descriptor.getName());
} catch (Exception e) {
logger.error("Failed to instantiate plugin class: {}", className, e);
}
}
} catch (ClassNotFoundException | NoClassDefFoundError e) {
logger.error("Error loading class: {}", className, e);
}
}
private static void loadMainClass(String mainClassName, File jarFile, PluginDescriptor descriptor) {
private static void loadMainClass(String mainClassName, PluginDescriptor descriptor) {
if (mainClassName == null || mainClassName.isEmpty()) {
logger.error("Invalid main class name: {}", mainClassName);
return;
}
try (URLClassLoader classLoader = new URLClassLoader(
new URL[]{jarFile.toURI().toURL()},
PluginLoader.class.getClassLoader())
) {
ClassLoader classLoader = descriptor.getClassLoader();
if (classLoader == null) {
logError("Plugin ClassLoader is missing for {}", descriptor.getName());
return;
}
try {
Class<?> mainClass = classLoader.loadClass(mainClassName);
Object instance = mainClass.getDeclaredConstructor().newInstance();
descriptor.setInstance(instance);
injectInstanceField(mainClass, instance, descriptor);
} catch (Exception e) {
logError("Failed to load main class: {}", mainClassName, e);
}
}
private static void injectInstanceField(Class<?> clazz, Object instance, PluginDescriptor descriptor) {
try {
Field pluginInstance = mainClass.getDeclaredField("INSTANCE");
Field pluginInstance = clazz.getDeclaredField("INSTANCE");
pluginInstance.setAccessible(true);
pluginInstance.set(null, descriptor);
} catch (NoSuchFieldException | IllegalArgumentException
| SecurityException | IllegalAccessException e) {
logger.warn("Failed to set plugin instance: {}", mainClassName, e);
}
} catch (NoSuchFieldException e) {
// 忽略
} catch (Exception e) {
logger.error("Failed to load main class: {}", mainClassName, e);
logWarn("Failed to set plugin instance field for {}", clazz.getName(), e);
}
}

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.register;
import com.axis.innovators.box.plugins.PluginDescriptor;
import com.axis.innovators.box.plugins.PluginLoader;
import com.axis.innovators.box.tools.FolderCreator;
import org.apache.logging.log4j.LogManager;
@@ -27,14 +28,54 @@ public class LanguageManager {
LANGUAGES.add(new Language("日本語", "system:ja_JP", "sys_ja_JP"));
}
/**
* 插件专用:从插件 Jar 包资源中快速注册/合并语言
* <p>
* 示例用法:
* LanguageManager.registerPluginLanguage(plugin, "assets/lang/en_US.properties", "system:en_US");
* </p>
*
* @param plugin 插件描述符(用于获取 ClassLoader
* @param resourcePath 资源在 Jar 包中的路径 (例如 "assets/lang/zh_CN.properties")
* @param targetRegisteredName 目标语言的注册名 (例如 "system:zh_CN"),将合并到此语言中
*/
public static void registerPluginLanguage(PluginDescriptor plugin, String resourcePath, String targetRegisteredName) {
if (plugin == null || resourcePath == null || targetRegisteredName == null) {
return;
}
try (InputStream is = plugin.getResourceAsStream(resourcePath)) {
if (is == null) {
logger.warn("无法在插件 [{}] 中找到语言资源: {}", plugin.getName(), resourcePath);
return;
}
// 创建一个临时的 Language 对象,重写加载逻辑以避免读取磁盘文件
Language pluginLang = new Language(
plugin.getName() + " Resource", // 临时名称,不重要
targetRegisteredName, // 关键:注册名必须与现有语言一致才能合并
null
) {
@Override
public void loadLanguageFile(String ignored) {
// 覆盖父类行为:不从磁盘加载,防止 FileNotFoundException
}
};
// 手动从流中加载属性
pluginLang.loadFromInputStream(is);
// 复用现有的合并逻辑,这样可以保持统一的日志记录
addLanguage(pluginLang);
} catch (IOException e) {
logger.error("加载插件语言资源失败: {} - {}", plugin.getName(), resourcePath, e);
}
}
/**
* 添加/合并语言资源
* @param language 要添加的语言对象
* 处理规则:
* 1. 注册名相同时保留原有基础信息
* 2. 相同键值的新内容覆盖旧内容
* 3. 详细记录新增、更新的键值数量
* 4. 提供关键示例用于调试
*/
public static void addLanguage(Language language) {
for (Language existing : LANGUAGES) {
@@ -61,19 +102,21 @@ public class LanguageManager {
existing.properties.setProperty(key, newValue);
}
// 仅当有变更时才构建日志,减少刷屏(可选优化)
if (added > 0 || updated > 0) {
String logDetail = buildMergeLogDetails(added, updated, sampleAddedKeys, sampleUpdatedKeys);
logger.info("【语言合并报告】\n" +
"▌合并目标{} ({})\n" +
"▌合并来源{} -> {}\n" +
"▌变更统计:新增 {} 条 / 更新 {} 条\n" +
"▌当前总量:{} 条\n" +
"▌关键示例:\n{}",
existing.getLanguageName(),
language.getLanguageName(),
existing.getRegisteredName(),
added,
updated,
existing.properties.size(),
logDetail);
}
return;
} catch (Exception e) {
@@ -122,54 +165,43 @@ public class LanguageManager {
*/
public static void loadLanguage(String languageName) {
loadedLanguages = LanguageManager.getLanguage(languageName);
if (loadedLanguages != null) {
saveCurrentLanguageToFile();
}
}
/**
* 将当前加载的语言保存到配置文件中
*/
private static void saveCurrentLanguageToFile() {
if (loadedLanguages == null) return;
Properties properties = new Properties();
properties.setProperty("loadedLanguage", loadedLanguages.getRegisteredName());
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(SAVED_LANGUAGE_FILE), StandardCharsets.UTF_8)) {
properties.store(writer, "Current Loaded Language");
} catch (IOException e) {
System.err.println("Failed to save current language to file: " + SAVED_LANGUAGE_FILE);
e.printStackTrace();
logger.error("Failed to save current language to file: {}", SAVED_LANGUAGE_FILE, e);
}
}
/**
* 从配置文件中加载保存的语言
*/
public static void loadSavedLanguage() {
File file = new File(SAVED_LANGUAGE_FILE);
if (!file.exists()) return;
Properties properties = new Properties();
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(SAVED_LANGUAGE_FILE), StandardCharsets.UTF_8)) {
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
properties.load(reader);
String loadedLanguageName = properties.getProperty("loadedLanguage");
if (loadedLanguageName != null) {
loadedLanguages = LanguageManager.getLanguage(loadedLanguageName);
}
} catch (IOException e) {
System.err.println("Failed to load saved language from file: " + SAVED_LANGUAGE_FILE);
e.printStackTrace();
logger.error("Failed to load saved language from file: {}", SAVED_LANGUAGE_FILE, e);
}
}
/**
* 获取已加载的语言
* @return 已加载的语言
*/
public static Language getLoadedLanguages() {
return loadedLanguages;
}
/**
* 获取语言
* @param languageName 语言注册名,或者语言名称
* @return 语言对象
*/
public static Language getLanguage(String languageName) {
for (Language language : LANGUAGES) {
if (language.getRegisteredName().equals(languageName)) {
@@ -185,14 +217,14 @@ public class LanguageManager {
return null;
}
/**
* 获取所有语言
* @return 所有语言
*/
public static List<Language> getLanguages() {
return LANGUAGES;
}
// ==========================================
// Inner Class Language
// ==========================================
public static class Language {
private final String languageName;
private final String registeredName;
@@ -210,14 +242,19 @@ public class LanguageManager {
this.languageFile = LANGUAGE_PATH + "\\" + languageFileName + ".properties";
}
this.properties = new Properties();
// 默认构造时加载文件
loadLanguageFile(languageFile);
}
/**
* 加载语言文件
* 从磁盘加载语言文件
*/
public void loadLanguageFile(String languageFile) {
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(languageFile),
File file = new File(languageFile);
if (!file.exists()) return; // 容错处理
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file),
StandardCharsets.UTF_8)) {
properties.load(reader);
} catch (IOException e) {
@@ -227,10 +264,19 @@ public class LanguageManager {
}
/**
* 获取指定键的文本
* @param key 键
* @return 对应的文本,如果键不存在则返回 null
* 新增:直接从输入流加载配置(供插件使用)
* @param inputStream 资源流
*/
public void loadFromInputStream(InputStream inputStream) {
if (inputStream == null) return;
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
properties.load(reader);
} catch (IOException e) {
System.err.println("Failed to load language from stream for " + registeredName);
e.printStackTrace();
}
}
public String getText(String key) {
if (!properties.containsKey(key)) {
return key;
@@ -238,9 +284,6 @@ public class LanguageManager {
return properties.getProperty(key);
}
/**
* 添加文本
*/
public void addText(String key, String value) {
properties.setProperty(key, value);
}

View File

@@ -465,7 +465,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
JPanel iconPanel = new JPanel();
ImageIcon icon = LoadIcon.loadIcon(plugin.getInstance().getClass(), plugin.getIcon(), 64);
ImageIcon icon = LoadIcon.loadIcon(plugin, plugin.getIcon(), 64);
JLabel iconLabel = new JLabel(icon);
iconPanel.add(iconLabel);
mainPanel.add(iconPanel, BorderLayout.WEST);

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.window;
import com.axis.innovators.box.plugins.PluginDescriptor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -11,67 +12,180 @@ import java.net.URL;
/**
* 负责加载图片
* <p>
* 更新说明:已适配 PluginDescriptor。
* </p>
* @author tzdwindows 7
*/
public class LoadIcon {
private static final Logger logger = LogManager.getLogger(LoadIcon.class);
private static final String ICON_PATH = "/icons/";
// ==========================================
// Public API - 面向 PluginDescriptor
// ==========================================
/**
* 加载图片
* 从插件资源中加载正方形图标
* @param plugin 插件描述符
* @param filename 图片文件名 (相对于插件 classpath 的 /icons/ 或根路径)
* @param size 宽高
* @return ImageIcon
*/
public static ImageIcon loadIcon(PluginDescriptor plugin, String filename, int size) {
return loadIcon(plugin, filename, size, size);
}
/**
* 从插件资源中加载指定宽高的图标
* @param plugin 插件描述符
* @param filename 图片文件名
* @param width 宽
* @param height 高
* @return ImageIcon
*/
public static ImageIcon loadIcon(PluginDescriptor plugin, String filename, int width, int height) {
ClassLoader loader = plugin.getClassLoader();
if (loader == null) {
logger.warn("Plugin {} has no ClassLoader, returning placeholder.", plugin.getName());
return createPlaceholderIcon(width, height);
}
return loadIconInternal(loader, filename, width, height);
}
// ==========================================
// Public API - 面向主程序 (默认使用 LoadIcon.class)
// ==========================================
/**
* 加载主程序图标
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
public static ImageIcon loadIcon(String filename, int size) {
return loadIcon(LoadIcon.class, filename, size);
return loadIconInternal(LoadIcon.class.getClassLoader(), filename, size, size);
}
/**
* 加载指定宽高的图片(适用于背景图)
* 加载主程序指定宽高的图片
* @param filename 图片名
* @param width 宽度
* @param height 高度
* @return ImageIcon对象
*/
public static ImageIcon loadIcon(String filename, int width, int height) {
return loadIcon(LoadIcon.class, filename, width, height);
return loadIconInternal(LoadIcon.class.getClassLoader(), filename, width, height);
}
/**
* 加载指定宽高的图片(核心构造体)
* @param clazz resources包所在的jar
* 加载系统包下的图片
* @param clazz resources包所在的jar
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
public static ImageIcon loadSystemIcon(Class<?> clazz, String filename, int size) {
return loadIconInternal(clazz.getClassLoader(), filename, size, size);
}
// ==========================================
// Package-Private API - 限制访问 (不支持 PluginDescriptor 的旧方法)
// ==========================================
/**
* 加载指定宽高的图片(仅限当前包访问)
* @param clazz resources包所在的jar类
* @param filename 图片名
* @param width 目标宽度
* @param height 目标高度
* @return ImageIcon对象
*/
public static ImageIcon loadIcon(Class<?> clazz, String filename, int width, int height) {
static ImageIcon loadIcon(Class<?> clazz, String filename, int width, int height) {
return loadIconInternal(clazz.getClassLoader(), filename, width, height);
}
/**
* 加载图片(仅限当前包访问)
* @param clazz resources包所在的jar类
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
static ImageIcon loadIcon(Class<?> clazz, String filename, int size) {
return loadIconInternal(clazz.getClassLoader(), filename, size, size);
}
/**
* 加载图片,另一个版本(仅限当前包访问)
* 直接加载 filename不尝试拼接 ICON_PATH
*/
static ImageIcon loadIcon0(Class<?> clazz, String filename, int size) {
try {
if (filename == null || filename.isEmpty()) {
return createPlaceholderIcon(size, size);
}
URL imgUrl = clazz.getClassLoader().getResource(filename); // 使用 ClassLoader 加载
if (imgUrl == null) {
// 回退尝试 class.getResource (处理相对路径差异)
imgUrl = clazz.getResource(filename);
}
if (imgUrl == null) {
return createPlaceholderIcon(size, size);
}
Image image = new ImageIcon(imgUrl).getImage();
return new ImageIcon(image.getScaledInstance(size, size, Image.SCALE_SMOOTH));
} catch (Exception e) {
logger.error("Failed to load icon0 '{}'", filename, e);
return createPlaceholderIcon(size, size);
}
}
// ==========================================
// Internal Core Logic
// ==========================================
/**
* 统一的核心加载逻辑
*/
private static ImageIcon loadIconInternal(ClassLoader classLoader, String filename, int width, int height) {
try {
if (filename == null || filename.isEmpty()) {
return createPlaceholderIcon(width, height);
}
Image image;
// 1. 处理绝对路径
if (new File(filename).isAbsolute()) {
image = new ImageIcon(filename).getImage();
} else {
// 1. 处理绝对路径 (不依赖 ClassLoader)
File file = new File(filename);
if (file.isAbsolute() && file.exists()) {
Image image = new ImageIcon(filename).getImage();
return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_SMOOTH));
}
// 2. 处理资源路径
URL imgUrl = null;
// 尝试 A: /icons/ + filename (标准化路径)
String fullPath = ICON_PATH + filename;
URL imgUrl = clazz.getResource(fullPath);
if (imgUrl == null) {
// 尝试不带 /icons/ 路径直接加载 (兼容 loadIcon0 逻辑)
imgUrl = clazz.getResource(filename);
// 去掉开头的 / 因为 ClassLoader.getResource 不以 / 开头
if (fullPath.startsWith("/")) fullPath = fullPath.substring(1);
if (classLoader != null) {
imgUrl = classLoader.getResource(fullPath);
}
// 尝试 B: 直接使用 filename (如果不带 path 或已经在根目录下)
if (imgUrl == null && classLoader != null) {
String cleanName = filename.startsWith("/") ? filename.substring(1) : filename;
imgUrl = classLoader.getResource(cleanName);
}
if (imgUrl == null) {
logger.warn("Resource not found: {}", filename);
return createPlaceholderIcon(width, height);
}
image = new ImageIcon(imgUrl).getImage();
}
Image image = new ImageIcon(imgUrl).getImage();
// 3. 执行高质量缩放
return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_SMOOTH));
@@ -88,81 +202,13 @@ public class LoadIcon {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
// 使用更符合现代深色主题的占位颜色
g2d.setColor(new Color(30, 30, 30));
g2d.setColor(new Color(30, 30, 30, 128)); // 半透明深色
g2d.fillRect(0, 0, width, height);
g2d.dispose();
return new ImageIcon(img);
}
/**
* 加载图片
* @param clazz resources包所在的jar
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
public static ImageIcon loadIcon(Class<?> clazz, String filename, int size) {
try {
if (filename.isEmpty()) {
return createPlaceholderIcon(size);
}
// 画个边框表示丢失
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, width - 1, height - 1);
if (new File(filename).isAbsolute()) {
return loadAbsolutePathIcon(filename, size);
}
String fullPath = ICON_PATH + filename;
URL imgUrl = clazz.getResource(fullPath);
if (imgUrl == null) {
return createPlaceholderIcon(size);
}
Image image = new ImageIcon(imgUrl).getImage();
return new ImageIcon(image.getScaledInstance(size, size, Image.SCALE_SMOOTH));
} catch (Exception e) {
logger.error("Failed to load icon image path '{}'", filename, e);
return createPlaceholderIcon(size);
}
}
private static ImageIcon loadAbsolutePathIcon(String absolutePath, int size) {
try {
Image image = new ImageIcon(absolutePath).getImage();
return new ImageIcon(image.getScaledInstance(size, size, Image.SCALE_SMOOTH));
} catch (Exception e) {
logger.error("Failed to load absolute path icon '{}'", absolutePath, e);
return createPlaceholderIcon(size);
}
}
/**
* 加载图片,另一个版本
* @param clazz resources包所在的jar
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
public static ImageIcon loadIcon0(Class<?> clazz ,String filename, int size) {
try {
if (filename.isEmpty()){
return createPlaceholderIcon(size);
}
URL imgUrl = clazz.getResource(filename);
if (imgUrl == null) {
return createPlaceholderIcon(size);
}
Image image = new ImageIcon(imgUrl).getImage();
return new ImageIcon(image.getScaledInstance(size, size, Image.SCALE_SMOOTH));
} catch (Exception e) {
logger.error("Failed to load icon image path '{}'", filename, e);
return createPlaceholderIcon(size);
}
}
private static ImageIcon createPlaceholderIcon(int size) {
BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillRect(0, 0, size, size);
g2d.dispose();
return new ImageIcon(img);
}

View File

@@ -1,4 +1,4 @@
# Auto-generated build information
version=0.0.1
buildTimestamp=2026-01-02T17:08:37.387878
buildTimestamp=2026-01-02T17:46:06.8226378
buildSystem=WINDOWS