mm_machine 2.8.3 → 2.8.5

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.
Files changed (3) hide show
  1. package/drive.js +25 -0
  2. package/index.js +164 -36
  3. package/package.json +1 -1
package/drive.js CHANGED
@@ -135,9 +135,34 @@ Drive.prototype.setConfig = function (config) {
135
135
  }
136
136
  }
137
137
  this._preset();
138
+ // 通知父级刷新快照
139
+ this._notifySnapshot();
138
140
  return this.config;
139
141
  };
140
142
 
143
+ /**
144
+ * 通知父级Manager刷新快照(防抖)
145
+ */
146
+ Drive.prototype._notifySnapshot = function () {
147
+ if (!this.getParent) {
148
+ return;
149
+ }
150
+ var parent = this.getParent();
151
+ if (!parent || !parent.config || !parent.config.use_snapshot) {
152
+ return;
153
+ }
154
+ // 防抖:避免频繁写文件,500ms内只刷新一次
155
+ if (this._snapshot_timer) {
156
+ return;
157
+ }
158
+ this._snapshot_timer = setTimeout(() => {
159
+ this._snapshot_timer = null;
160
+ if (parent.refreshSnapshot) {
161
+ parent.refreshSnapshot();
162
+ }
163
+ }, 500);
164
+ };
165
+
141
166
  /**
142
167
  * 获取配置
143
168
  * @returns {object} 配置项
package/index.js CHANGED
@@ -86,10 +86,10 @@ class Manager extends Mod {
86
86
  */
87
87
  use_snapshot: true,
88
88
  /**
89
- * 快照文件路径
89
+ * 快照文件路径,留空则自动生成到 cache/snapshot/ 目录下
90
90
  * @type {string}
91
91
  */
92
- snapshot_file: './snapshot.json'
92
+ snapshot_file: ''
93
93
  };
94
94
 
95
95
  /**
@@ -479,23 +479,30 @@ Manager.prototype._findFiles = async function (dir) {
479
479
 
480
480
  /**
481
481
  * 获取快照文件路径
482
+ * 默认统一放到 /cache/snapshot/ 目录,文件名由类名+配置名组成
482
483
  * @returns {string} 快照文件完整路径
483
484
  */
484
485
  Manager.prototype.getSnapshotPath = function () {
485
- var snapshot_file = this.config.snapshot_file || './snapshot.json';
486
- let dir = this.getBaseDir();
487
- return snapshot_file.fullname(dir);
486
+ var snapshot_file = this.config.snapshot_file;
487
+ if (!snapshot_file) {
488
+ var class_name = this.constructor.name.toLowerCase();
489
+ var name = this.config.name || 'default';
490
+ snapshot_file = `./cache/snapshot/${class_name}_${name}.json`;
491
+ // 确保目录存在
492
+ snapshot_file.addDir();
493
+ }
494
+ return snapshot_file.fullname();
488
495
  };
489
496
 
490
497
  /**
491
498
  * 保存快照数据
492
- * @param {object} files_map 目录到文件列表的映射 { dir: [file_path, ...] }
499
+ * @param {object} mods_map 目录到模块条目的映射 { dir: [{ file, config }, ...] }
493
500
  */
494
- Manager.prototype._saveSnapshot = function (files_map) {
501
+ Manager.prototype._saveSnapshot = function (mods_map) {
495
502
  try {
496
503
  var snapshot_path = this.getSnapshotPath();
497
504
  var snapshot = {
498
- files_map: files_map,
505
+ mods_map: mods_map,
499
506
  updated_at: Date.now()
500
507
  };
501
508
  snapshot_path.saveJson(snapshot);
@@ -504,6 +511,47 @@ Manager.prototype._saveSnapshot = function (files_map) {
504
511
  }
505
512
  };
506
513
 
514
+ /**
515
+ * 从当前已加载模块刷新快照
516
+ * 确保快照与内存中模块状态一致
517
+ */
518
+ Manager.prototype.refreshSnapshot = function () {
519
+ if (!this.config.use_snapshot) {
520
+ return;
521
+ }
522
+ var mods_map = {};
523
+ for (var name in this.mods) {
524
+ var mod = this.mods[name];
525
+ var f = mod.config_file;
526
+ if (!f) {
527
+ continue;
528
+ }
529
+ var dir = this._findSnapshotDir(f);
530
+ if (!mods_map[dir]) {
531
+ mods_map[dir] = [];
532
+ }
533
+ mods_map[dir].push({ file: f, config: mod.config });
534
+ }
535
+ this._saveSnapshot(mods_map);
536
+ };
537
+
538
+ /**
539
+ * 推断配置文件所属的快照检索目录
540
+ * @param {string} file 配置文件路径
541
+ * @returns {string} 检索目录
542
+ */
543
+ Manager.prototype._findSnapshotDir = function (file) {
544
+ var base_dir = this.getBaseDir();
545
+ if (base_dir && file.startsWith(base_dir)) {
546
+ return base_dir;
547
+ }
548
+ var dir = this.getDir();
549
+ if (dir && file.startsWith(dir)) {
550
+ return dir;
551
+ }
552
+ return file.dirname();
553
+ };
554
+
507
555
  /**
508
556
  * 加载快照数据
509
557
  * @returns {object|null} 快照数据,不存在时返回null
@@ -515,7 +563,7 @@ Manager.prototype._loadSnapshot = function () {
515
563
  return null;
516
564
  }
517
565
  var snapshot = snapshot_path.loadJson();
518
- if (!snapshot || !snapshot.files_map) {
566
+ if (!snapshot || !snapshot.mods_map) {
519
567
  return null;
520
568
  }
521
569
  return snapshot;
@@ -526,31 +574,68 @@ Manager.prototype._loadSnapshot = function () {
526
574
  };
527
575
 
528
576
  /**
529
- * 从快照快速加载模块,不进行文件系统扫描
530
- * @param {object} files_map 目录到文件列表的映射
577
+ * 从快照快速加载模块,不进行文件系统扫描和磁盘配置读取
578
+ * 使用快照中的config直接创建模块,跳过loadConfig的IO
579
+ * @param {object} mods_map 目录到模块条目的映射 { dir: [{ file, config }, ...] }
531
580
  * @param {boolean} clear 是否清除已有模块
532
581
  */
533
- Manager.prototype._loadFromSnapshot = function (files_map, clear = true) {
582
+ Manager.prototype._loadFromSnapshot = function (mods_map, clear = true) {
534
583
  if (clear) {
535
584
  this.clearMods();
536
585
  }
537
- for (var dir in files_map) {
538
- var files = files_map[dir];
539
- if (files && files.length > 0) {
540
- this.registerMods(files);
586
+ for (var dir in mods_map) {
587
+ var entries = mods_map[dir];
588
+ if (!entries || entries.length === 0) {
589
+ continue;
590
+ }
591
+ for (var i = 0; i < entries.length; i++) {
592
+ var entry = entries[i];
593
+ var f = entry.file;
594
+ // 跳过已不存在的文件,等待后台同步处理
595
+ if (!f.hasFile || !f.hasFile()) {
596
+ continue;
597
+ }
598
+ this._newModFromSnap(f, entry.config);
541
599
  }
542
600
  }
543
601
  this.updateInfo();
544
602
  this.is_loaded = true;
545
603
  };
546
604
 
605
+ /**
606
+ * 用快照config创建模块,跳过磁盘读取
607
+ * @param {string} file 配置文件路径
608
+ * @param {object} config 模块配置
609
+ * @returns {object} 模块对象
610
+ */
611
+ Manager.prototype._newModFromSnap = function (file, config) {
612
+ var mod;
613
+ if (this.config.mod_type === 'script') {
614
+ mod = new Mod(config || {}, this);
615
+ } else {
616
+ mod = new this.Drive(config || {}, this);
617
+ }
618
+ mod.mode = this.config.mode;
619
+ mod.config_file = file;
620
+ if (mod._preset) {
621
+ mod._preset();
622
+ }
623
+ return this.setMod(mod.config.name, mod);
624
+ };
625
+
547
626
  /**
548
627
  * 后台增量同步:扫描文件系统并与快照进行diff,增减模块
628
+ * 使用锁防止多次重入冲突
549
629
  * @param {object} snapshot 快照数据
550
630
  */
551
631
  Manager.prototype._syncFromFile = async function (snapshot) {
632
+ // 防重入锁,避免多次同步同时执行
633
+ if (this._syncing) {
634
+ return;
635
+ }
636
+ this._syncing = true;
552
637
  try {
553
- var old_dirs = snapshot.files_map || {};
638
+ var old_dirs = snapshot.mods_map || {};
554
639
  var old = this._buildSet(old_dirs);
555
640
 
556
641
  // 扫描目录并注册新模块
@@ -567,25 +652,28 @@ Manager.prototype._syncFromFile = async function (snapshot) {
567
652
  }
568
653
 
569
654
  // 移除已删除的模块
570
- this._removeStale(old);
655
+ await this._removeStale(old);
571
656
  this.updateInfo();
572
657
  await this._saveSnapshotDirs(Object.keys(old_dirs));
658
+ this.log('info', '快照同步完成');
573
659
  } catch (err) {
574
660
  this.log('error', '快照增量同步失败', err);
661
+ } finally {
662
+ this._syncing = false;
575
663
  }
576
664
  };
577
665
 
578
666
  /**
579
667
  * 构建文件集合
580
- * @param {object} dirs 目录到文件列表的映射
668
+ * @param {object} dirs 目录到模块条目的映射 { dir: [{ file, config }, ...] }
581
669
  * @returns {object} 文件集合 { file: true }
582
670
  */
583
671
  Manager.prototype._buildSet = function (dirs) {
584
672
  var set = {};
585
673
  for (var dir in dirs) {
586
- var files = dirs[dir];
587
- for (var i = 0; i < files.length; i++) {
588
- set[files[i]] = true;
674
+ var entries = dirs[dir];
675
+ for (var i = 0; i < entries.length; i++) {
676
+ set[entries[i].file] = true;
589
677
  }
590
678
  }
591
679
  return set;
@@ -595,12 +683,12 @@ Manager.prototype._buildSet = function (dirs) {
595
683
  * 移除已删除的模块
596
684
  * @param {object} old 旧文件集合,标记为false表示仍存在
597
685
  */
598
- Manager.prototype._removeStale = function (old) {
686
+ Manager.prototype._removeStale = async function (old) {
599
687
  for (var file in old) {
600
688
  if (old[file] === true) {
601
689
  var mod = this.getMod(file);
602
690
  if (mod) {
603
- this.unloadMod(mod.config.name);
691
+ await this.unloadMod(mod.config.name);
604
692
  this.delMod(mod.config.name);
605
693
  this.log('info', '快照同步: 移除模块 ' + file);
606
694
  }
@@ -609,16 +697,52 @@ Manager.prototype._removeStale = function (old) {
609
697
  };
610
698
 
611
699
  /**
612
- * 保存快照目录
700
+ * 构建文件到config的映射
701
+ * @returns {object} 文件到config的映射 { file_path: config }
702
+ */
703
+ Manager.prototype._buildConfigMap = function () {
704
+ var map = {};
705
+ var mods = this.getMods();
706
+ for (var name in mods) {
707
+ var mod = mods[name];
708
+ if (mod.config_file) {
709
+ map[mod.config_file] = mod.config;
710
+ }
711
+ }
712
+ return map;
713
+ };
714
+
715
+ /**
716
+ * 构建模块条目列表
717
+ * @param {string[]} files 文件路径数组
718
+ * @param {object} config_dict 文件到config的映射
719
+ * @returns {object[]} 模块条目数组 [{ file, config }]
720
+ */
721
+ Manager.prototype._buildEntries = function (files, config_dict) {
722
+ var entries = [];
723
+ for (var i = 0; i < files.length; i++) {
724
+ var f = files[i];
725
+ var config = config_dict[f];
726
+ if (config) {
727
+ entries.push({ file: f, config: config });
728
+ }
729
+ }
730
+ return entries;
731
+ };
732
+
733
+ /**
734
+ * 保存快照目录(含config)
613
735
  * @param {string[]} dirs 目录列表
614
736
  */
615
737
  Manager.prototype._saveSnapshotDirs = async function (dirs) {
616
- var new_dirs = {};
738
+ var new_map = {};
739
+ var config_dict = this._buildConfigMap();
617
740
  for (var i = 0; i < dirs.length; i++) {
618
741
  var dir = dirs[i];
619
- new_dirs[dir] = await this._findFiles(dir);
742
+ var files = await this._findFiles(dir);
743
+ new_map[dir] = this._buildEntries(files, config_dict);
620
744
  }
621
- this._saveSnapshot(new_dirs);
745
+ this._saveSnapshot(new_map);
622
746
  };
623
747
 
624
748
  /**
@@ -757,7 +881,7 @@ Manager.prototype._runMod = async function (mod, method, ...params) {
757
881
  /**
758
882
  * 初始化管理器
759
883
  */
760
- Manager.prototype._initManager = function () {};
884
+ Manager.prototype._initManager = function () { };
761
885
 
762
886
  /**
763
887
  * 获取公共模块目录
@@ -786,8 +910,8 @@ Manager.prototype._loadSources = async function () {
786
910
  if (this.config.use_snapshot) {
787
911
  var snapshot = this._loadSnapshot();
788
912
  if (snapshot) {
789
- // 快照存在,快速加载模块
790
- this._loadFromSnapshot(snapshot.files_map);
913
+ // 快照存在,用config快速加载模块
914
+ this._loadFromSnapshot(snapshot.mods_map);
791
915
  this.log('info', '已从快照快速加载模块,正在后台同步文件变更...');
792
916
  // 异步增量同步,不阻塞启动
793
917
  this._syncFromFile(snapshot);
@@ -803,14 +927,16 @@ Manager.prototype._loadSources = async function () {
803
927
  await this.call('update', dir, true);
804
928
  }
805
929
 
806
- // 首次扫描后保存快照
930
+ // 首次扫描后保存快照(含config)
807
931
  if (this.config.use_snapshot) {
808
- var files_map = {};
932
+ var dirs = [];
809
933
  if (base_dir) {
810
- files_map[base_dir] = await this._findFiles(base_dir);
934
+ dirs.push(base_dir);
935
+ }
936
+ if (dir) {
937
+ dirs.push(dir);
811
938
  }
812
- files_map[dir] = await this._findFiles(dir);
813
- this._saveSnapshot(files_map);
939
+ await this._saveSnapshotDirs(dirs);
814
940
  }
815
941
  };
816
942
 
@@ -1148,6 +1274,7 @@ Manager.prototype.createFile = function (config, file) {
1148
1274
  Manager.prototype.create = function (config, file = '') {
1149
1275
  let f = this.createFile(config, file);
1150
1276
  let mod = this.registerMod(f, config);
1277
+ this.refreshSnapshot();
1151
1278
  return mod;
1152
1279
  };
1153
1280
 
@@ -1162,6 +1289,7 @@ Manager.prototype.remove = async function (name) {
1162
1289
  await mod.remove();
1163
1290
  }
1164
1291
  this.unregisterMod(name);
1292
+ this.refreshSnapshot();
1165
1293
  return mod;
1166
1294
  };
1167
1295
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mm_machine",
3
- "version": "2.8.3",
3
+ "version": "2.8.5",
4
4
  "description": "A flexible Node.js plugin mechanism system for dynamic loading, management and execution of modules. Supports hot reload, lifecycle management, and modern JavaScript features.",
5
5
  "main": "index.js",
6
6
  "scripts": {