mm_machine 2.8.2 → 2.8.4

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 +153 -31
  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
@@ -483,18 +483,19 @@ Manager.prototype._findFiles = async function (dir) {
483
483
  */
484
484
  Manager.prototype.getSnapshotPath = function () {
485
485
  var snapshot_file = this.config.snapshot_file || './snapshot.json';
486
- return snapshot_file.fullname(__dirname);
486
+ let dir = this.getBaseDir();
487
+ return snapshot_file.fullname(dir);
487
488
  };
488
489
 
489
490
  /**
490
491
  * 保存快照数据
491
- * @param {object} files_map 目录到文件列表的映射 { dir: [file_path, ...] }
492
+ * @param {object} mods_map 目录到模块条目的映射 { dir: [{ file, config }, ...] }
492
493
  */
493
- Manager.prototype._saveSnapshot = function (files_map) {
494
+ Manager.prototype._saveSnapshot = function (mods_map) {
494
495
  try {
495
496
  var snapshot_path = this.getSnapshotPath();
496
497
  var snapshot = {
497
- files_map: files_map,
498
+ mods_map: mods_map,
498
499
  updated_at: Date.now()
499
500
  };
500
501
  snapshot_path.saveJson(snapshot);
@@ -503,6 +504,47 @@ Manager.prototype._saveSnapshot = function (files_map) {
503
504
  }
504
505
  };
505
506
 
507
+ /**
508
+ * 从当前已加载模块刷新快照
509
+ * 确保快照与内存中模块状态一致
510
+ */
511
+ Manager.prototype.refreshSnapshot = function () {
512
+ if (!this.config.use_snapshot) {
513
+ return;
514
+ }
515
+ var mods_map = {};
516
+ for (var name in this.mods) {
517
+ var mod = this.mods[name];
518
+ var f = mod.config_file;
519
+ if (!f) {
520
+ continue;
521
+ }
522
+ var dir = this._findSnapshotDir(f);
523
+ if (!mods_map[dir]) {
524
+ mods_map[dir] = [];
525
+ }
526
+ mods_map[dir].push({ file: f, config: mod.config });
527
+ }
528
+ this._saveSnapshot(mods_map);
529
+ };
530
+
531
+ /**
532
+ * 推断配置文件所属的快照检索目录
533
+ * @param {string} file 配置文件路径
534
+ * @returns {string} 检索目录
535
+ */
536
+ Manager.prototype._findSnapshotDir = function (file) {
537
+ var base_dir = this.getBaseDir();
538
+ if (base_dir && file.startsWith(base_dir)) {
539
+ return base_dir;
540
+ }
541
+ var dir = this.getDir();
542
+ if (dir && file.startsWith(dir)) {
543
+ return dir;
544
+ }
545
+ return file.dirname();
546
+ };
547
+
506
548
  /**
507
549
  * 加载快照数据
508
550
  * @returns {object|null} 快照数据,不存在时返回null
@@ -514,7 +556,7 @@ Manager.prototype._loadSnapshot = function () {
514
556
  return null;
515
557
  }
516
558
  var snapshot = snapshot_path.loadJson();
517
- if (!snapshot || !snapshot.files_map) {
559
+ if (!snapshot || !snapshot.mods_map) {
518
560
  return null;
519
561
  }
520
562
  return snapshot;
@@ -525,31 +567,68 @@ Manager.prototype._loadSnapshot = function () {
525
567
  };
526
568
 
527
569
  /**
528
- * 从快照快速加载模块,不进行文件系统扫描
529
- * @param {object} files_map 目录到文件列表的映射
570
+ * 从快照快速加载模块,不进行文件系统扫描和磁盘配置读取
571
+ * 使用快照中的config直接创建模块,跳过loadConfig的IO
572
+ * @param {object} mods_map 目录到模块条目的映射 { dir: [{ file, config }, ...] }
530
573
  * @param {boolean} clear 是否清除已有模块
531
574
  */
532
- Manager.prototype._loadFromSnapshot = function (files_map, clear = true) {
575
+ Manager.prototype._loadFromSnapshot = function (mods_map, clear = true) {
533
576
  if (clear) {
534
577
  this.clearMods();
535
578
  }
536
- for (var dir in files_map) {
537
- var files = files_map[dir];
538
- if (files && files.length > 0) {
539
- this.registerMods(files);
579
+ for (var dir in mods_map) {
580
+ var entries = mods_map[dir];
581
+ if (!entries || entries.length === 0) {
582
+ continue;
583
+ }
584
+ for (var i = 0; i < entries.length; i++) {
585
+ var entry = entries[i];
586
+ var f = entry.file;
587
+ // 跳过已不存在的文件,等待后台同步处理
588
+ if (!f.hasFile || !f.hasFile()) {
589
+ continue;
590
+ }
591
+ this._newModFromSnap(f, entry.config);
540
592
  }
541
593
  }
542
594
  this.updateInfo();
543
595
  this.is_loaded = true;
544
596
  };
545
597
 
598
+ /**
599
+ * 用快照config创建模块,跳过磁盘读取
600
+ * @param {string} file 配置文件路径
601
+ * @param {object} config 模块配置
602
+ * @returns {object} 模块对象
603
+ */
604
+ Manager.prototype._newModFromSnap = function (file, config) {
605
+ var mod;
606
+ if (this.config.mod_type === 'script') {
607
+ mod = new Mod(config || {}, this);
608
+ } else {
609
+ mod = new this.Drive(config || {}, this);
610
+ }
611
+ mod.mode = this.config.mode;
612
+ mod.config_file = file;
613
+ if (mod._preset) {
614
+ mod._preset();
615
+ }
616
+ return this.setMod(mod.config.name, mod);
617
+ };
618
+
546
619
  /**
547
620
  * 后台增量同步:扫描文件系统并与快照进行diff,增减模块
621
+ * 使用锁防止多次重入冲突
548
622
  * @param {object} snapshot 快照数据
549
623
  */
550
624
  Manager.prototype._syncFromFile = async function (snapshot) {
625
+ // 防重入锁,避免多次同步同时执行
626
+ if (this._syncing) {
627
+ return;
628
+ }
629
+ this._syncing = true;
551
630
  try {
552
- var old_dirs = snapshot.files_map || {};
631
+ var old_dirs = snapshot.mods_map || {};
553
632
  var old = this._buildSet(old_dirs);
554
633
 
555
634
  // 扫描目录并注册新模块
@@ -566,25 +645,28 @@ Manager.prototype._syncFromFile = async function (snapshot) {
566
645
  }
567
646
 
568
647
  // 移除已删除的模块
569
- this._removeStale(old);
648
+ await this._removeStale(old);
570
649
  this.updateInfo();
571
650
  await this._saveSnapshotDirs(Object.keys(old_dirs));
651
+ this.log('info', '快照同步完成');
572
652
  } catch (err) {
573
653
  this.log('error', '快照增量同步失败', err);
654
+ } finally {
655
+ this._syncing = false;
574
656
  }
575
657
  };
576
658
 
577
659
  /**
578
660
  * 构建文件集合
579
- * @param {object} dirs 目录到文件列表的映射
661
+ * @param {object} dirs 目录到模块条目的映射 { dir: [{ file, config }, ...] }
580
662
  * @returns {object} 文件集合 { file: true }
581
663
  */
582
664
  Manager.prototype._buildSet = function (dirs) {
583
665
  var set = {};
584
666
  for (var dir in dirs) {
585
- var files = dirs[dir];
586
- for (var i = 0; i < files.length; i++) {
587
- set[files[i]] = true;
667
+ var entries = dirs[dir];
668
+ for (var i = 0; i < entries.length; i++) {
669
+ set[entries[i].file] = true;
588
670
  }
589
671
  }
590
672
  return set;
@@ -594,12 +676,12 @@ Manager.prototype._buildSet = function (dirs) {
594
676
  * 移除已删除的模块
595
677
  * @param {object} old 旧文件集合,标记为false表示仍存在
596
678
  */
597
- Manager.prototype._removeStale = function (old) {
679
+ Manager.prototype._removeStale = async function (old) {
598
680
  for (var file in old) {
599
681
  if (old[file] === true) {
600
682
  var mod = this.getMod(file);
601
683
  if (mod) {
602
- this.unloadMod(mod.config.name);
684
+ await this.unloadMod(mod.config.name);
603
685
  this.delMod(mod.config.name);
604
686
  this.log('info', '快照同步: 移除模块 ' + file);
605
687
  }
@@ -608,16 +690,52 @@ Manager.prototype._removeStale = function (old) {
608
690
  };
609
691
 
610
692
  /**
611
- * 保存快照目录
693
+ * 构建文件到config的映射
694
+ * @returns {object} 文件到config的映射 { file_path: config }
695
+ */
696
+ Manager.prototype._buildConfigMap = function () {
697
+ var map = {};
698
+ var mods = this.getMods();
699
+ for (var name in mods) {
700
+ var mod = mods[name];
701
+ if (mod.config_file) {
702
+ map[mod.config_file] = mod.config;
703
+ }
704
+ }
705
+ return map;
706
+ };
707
+
708
+ /**
709
+ * 构建模块条目列表
710
+ * @param {string[]} files 文件路径数组
711
+ * @param {object} config_dict 文件到config的映射
712
+ * @returns {object[]} 模块条目数组 [{ file, config }]
713
+ */
714
+ Manager.prototype._buildEntries = function (files, config_dict) {
715
+ var entries = [];
716
+ for (var i = 0; i < files.length; i++) {
717
+ var f = files[i];
718
+ var config = config_dict[f];
719
+ if (config) {
720
+ entries.push({ file: f, config: config });
721
+ }
722
+ }
723
+ return entries;
724
+ };
725
+
726
+ /**
727
+ * 保存快照目录(含config)
612
728
  * @param {string[]} dirs 目录列表
613
729
  */
614
730
  Manager.prototype._saveSnapshotDirs = async function (dirs) {
615
- var new_dirs = {};
731
+ var new_map = {};
732
+ var config_dict = this._buildConfigMap();
616
733
  for (var i = 0; i < dirs.length; i++) {
617
734
  var dir = dirs[i];
618
- new_dirs[dir] = await this._findFiles(dir);
735
+ var files = await this._findFiles(dir);
736
+ new_map[dir] = this._buildEntries(files, config_dict);
619
737
  }
620
- this._saveSnapshot(new_dirs);
738
+ this._saveSnapshot(new_map);
621
739
  };
622
740
 
623
741
  /**
@@ -785,8 +903,8 @@ Manager.prototype._loadSources = async function () {
785
903
  if (this.config.use_snapshot) {
786
904
  var snapshot = this._loadSnapshot();
787
905
  if (snapshot) {
788
- // 快照存在,快速加载模块
789
- this._loadFromSnapshot(snapshot.files_map);
906
+ // 快照存在,用config快速加载模块
907
+ this._loadFromSnapshot(snapshot.mods_map);
790
908
  this.log('info', '已从快照快速加载模块,正在后台同步文件变更...');
791
909
  // 异步增量同步,不阻塞启动
792
910
  this._syncFromFile(snapshot);
@@ -802,14 +920,16 @@ Manager.prototype._loadSources = async function () {
802
920
  await this.call('update', dir, true);
803
921
  }
804
922
 
805
- // 首次扫描后保存快照
923
+ // 首次扫描后保存快照(含config)
806
924
  if (this.config.use_snapshot) {
807
- var files_map = {};
925
+ var dirs = [];
808
926
  if (base_dir) {
809
- files_map[base_dir] = await this._findFiles(base_dir);
927
+ dirs.push(base_dir);
928
+ }
929
+ if (dir) {
930
+ dirs.push(dir);
810
931
  }
811
- files_map[dir] = await this._findFiles(dir);
812
- this._saveSnapshot(files_map);
932
+ await this._saveSnapshotDirs(dirs);
813
933
  }
814
934
  };
815
935
 
@@ -1147,6 +1267,7 @@ Manager.prototype.createFile = function (config, file) {
1147
1267
  Manager.prototype.create = function (config, file = '') {
1148
1268
  let f = this.createFile(config, file);
1149
1269
  let mod = this.registerMod(f, config);
1270
+ this.refreshSnapshot();
1150
1271
  return mod;
1151
1272
  };
1152
1273
 
@@ -1161,6 +1282,7 @@ Manager.prototype.remove = async function (name) {
1161
1282
  await mod.remove();
1162
1283
  }
1163
1284
  this.unregisterMod(name);
1285
+ this.refreshSnapshot();
1164
1286
  return mod;
1165
1287
  };
1166
1288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mm_machine",
3
- "version": "2.8.2",
3
+ "version": "2.8.4",
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": {