jsir 3.1.2 → 3.1.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/cmd/oaa.js CHANGED
@@ -13,7 +13,7 @@ const {
13
13
  getFullPath, parseUniqueName, toUniqueName, isJsirFileName, toJsirFileName,
14
14
  getAlias, wrapperJsirText, eia, getKeyTips, getValTips, getJsirTypeKey,
15
15
  createDetachedProcess, interceptStdStreams,
16
- draftModify, isRunningInBackground, fileJson, fileLock, processLock, cleanFileLocks, getMd5Key, terminalTitle,
16
+ draftModify, isRunningInBackground, fileJson, fileLock, processLock, getMd5Key, terminalTitle,
17
17
  getFileOpenExe, formatSec, formatMb
18
18
  } = $lib;
19
19
  const _args = process.argv.slice(2).map(trim);
@@ -150,7 +150,7 @@ const $data = {
150
150
  return _dealOnLazyGet(key, onLazyTempCode);
151
151
  }
152
152
  let result = null;
153
- await processLock('gafs:' + key, async () => {
153
+ await processLock('jsir:gafs:' + key, async () => {
154
154
  if (!vl(_data[key])) {
155
155
  result = _dataSet(key, await fn(), onLazyTempCode)
156
156
  } else {
@@ -203,7 +203,6 @@ function checkWorkspaces() {
203
203
 
204
204
  async function start() {
205
205
  terminalTitle()
206
- cleanFileLocks();
207
206
 
208
207
  setting.wrapperInput = wrapperInput;
209
208
  interceptStdStreams()
package/deps/room.js CHANGED
@@ -2,8 +2,8 @@ const os = require('os');
2
2
  const {fileJson, fileLock, vl, getKeyTips, getValTips,
3
3
  getRoomsDir, e, isRunningInBackground,
4
4
  batchAsync, trim, getOr, errorTag, warnStr, getConfig, getConfigDir,
5
- fileExist, processLock, isPidAlive, cleanFileLocks,
6
- roomConsole: console, reget, md5
5
+ fileExist, processLock, isPidAlive, getLockDir,
6
+ roomConsole: console, reget, md5, cacheFn
7
7
  } = require('./util');
8
8
  const {setRoute, createSign} = require('../deps/server')
9
9
  const roomDataFile = "jsirRoom.json"
@@ -340,10 +340,58 @@ async function processTasks(defTasks) {
340
340
  })
341
341
  }
342
342
 
343
- async function initRoom() {
344
- if (process.pid === setting.defaultPort) {
345
- cleanFileLocks();
343
+ function cleanFnCache() {
344
+ const now = Date.now()
345
+ for (let key of Object.keys(setting.fnCache)) {
346
+ let item = setting.fnCache[key]
347
+ if (item && item.validMsTime && now > item.validMsTime) {
348
+ delete setting.fnCache[key]
349
+ }
350
+ }
351
+ console.$debug("cleanFnCache cost", `${Date.now() - now}ms`)
352
+ }
353
+
354
+ async function readLockPid(lockDir) {
355
+ try {
356
+ const files = await fp.readdir(lockDir);
357
+ const pidFile = files.find(name => name.startsWith('pid-'));
358
+ if (!pidFile) {
359
+ return null;
360
+ }
361
+ return pidFile.split("-")[1];
362
+ } catch (err) {
363
+ // 读取目录出错(可能不存在?)直接返回 null
364
+ return null;
346
365
  }
366
+ }
367
+
368
+ async function cleanFileLocks() {
369
+ const now = Date.now()
370
+ let lockDir = getLockDir();
371
+ const files = await fp.readdir(lockDir);
372
+ const deadPid = {}
373
+ for (let file of files) {
374
+ let lockKeyDir = lockDir + '/' + file;
375
+ let pid = await readLockPid(lockKeyDir);
376
+ if (!pid) {
377
+ continue;
378
+ }
379
+ if (deadPid[pid] || !isPidAlive(pid)) {
380
+ deadPid[pid] = true;
381
+ // 持有锁的进程已经没了,则删除锁文件
382
+ try {
383
+ await fp.rm(lockKeyDir, { recursive: true, force: true });
384
+ } catch (err) {
385
+ console.$error(`cleanLock ${lockKeyDir} failed`, err);
386
+ }
387
+ }
388
+ }
389
+ console.$debug("cleanFileLocks cost", `${Date.now() - now}ms`)
390
+ }
391
+
392
+ async function initRoom() {
393
+ // 每分钟清理缓存
394
+ cacheFn("jsir:cleanFnCache", cleanFnCache, 60_000)
347
395
  setting.roomTime = Date.now();
348
396
  setting.nodeMap = await fileJson(jsirNodesFile);
349
397
  let roomUpdateTouchTime = false;
@@ -355,6 +403,8 @@ async function initRoom() {
355
403
  }
356
404
  })
357
405
  if (roomUpdateTouchTime) {
406
+ // 每分钟清理lock 文件
407
+ cacheFn("jsir:cleanFileLocks", cleanFileLocks, 60_000)
358
408
  fileLock(updateRoomInfoLockKey, async () => {
359
409
  let pros = []
360
410
  pros.push(syncRooms())
package/deps/setting.js CHANGED
@@ -52,5 +52,6 @@ module.exports = {
52
52
  newError: false,
53
53
  fnCache: {},
54
54
  syncQueues: {},
55
- tipsOnRm: {}
55
+ tipsOnRm: {},
56
+ locks: {}
56
57
  }
package/deps/util.js CHANGED
@@ -627,11 +627,12 @@ async function draftModify(fLine) {
627
627
  console.info("removed")
628
628
  }
629
629
  if (isEdit && results.length === 1) {
630
- let tempPath = getLibDataDir() + "/log/draft.temp";
630
+ let tempPath = getTempDir() + '/draft_' + uid();
631
631
  fs.writeFileSync(tempPath, results[0].split(/\n/).slice(1).join('\n'))
632
632
  await eia(getEditor(), [`"${tempPath}"`], true)
633
633
  let tempText = String(fs.readFileSync(tempPath))
634
- fs.writeFileSync(tempPath, '')
634
+ fs.unlinkSync(tempPath)
635
+
635
636
  let lineRange = lineRanges.get(results[0]);
636
637
  let before = allLines.filter((_, index) => {
637
638
  return index < lineRange.start;
@@ -705,21 +706,21 @@ function getVl(...obj) {
705
706
 
706
707
  async function cacheFn(key, fn, validMs = 0, awaitRefresh = true) {
707
708
  const cacheItem = getOr(setting.fnCache, key, {
708
- createdAt: 0,
709
+ validMsTime: 0,
709
710
  });
710
- if (Date.now() - cacheItem.createdAt > validMs) {
711
- // 临时设为已创建,防止并发请求多次调用 fn
712
- cacheItem.createdAt = Infinity;
711
+ if (Date.now() > cacheItem.validMsTime) {
712
+ cacheItem.validMsTime = Infinity;
713
713
  cacheItem.promise = (async () => {
714
714
  let val;
715
715
  try {
716
716
  val = await fn()
717
- } finally {
718
- cacheItem.createdAt = 0; // 恢复为无效状态
717
+ } catch (e) {
718
+ delete setting.fnCache[key]
719
+ throw e
719
720
  }
720
721
  cacheItem.val = val;
721
722
  cacheItem.valInit = true;
722
- cacheItem.createdAt = Date.now(); // 设置新的创建时间
723
+ cacheItem.validMsTime = Date.now() + validMs;
723
724
  })();
724
725
  }
725
726
  if (awaitRefresh || !cacheItem.valInit) {
@@ -770,13 +771,13 @@ function clearConsole(cleanType = 0) {
770
771
  if (cleanType === 0) {
771
772
  // \033[2J:清空屏幕。
772
773
  // \033[H:将光标定位到屏幕左上角。
773
- process.stdout.write('\033[2J\033[H'); // 展开新的屏幕,信息都保留
774
+ process.stdout.write('\x1B[2J\x1B[H'); // 展开新的屏幕,信息都保留
774
775
  } else if (cleanType === 1) {
775
776
  // \033[0f 清除从光标位置到屏幕末尾的内容,并把光标移动到屏幕的左上角。
776
777
  // process.stdout.write('\033[0f');
777
778
  console.clear(); // 清空当前屏幕
778
779
  } else if (cleanType > 1) {
779
- process.stdout.write('\x1B[3J\033[H'); // 全清,包括历史记录
780
+ process.stdout.write('\x1B[3J\x1B[H'); // 全清,包括历史记录
780
781
  console.clear();
781
782
  }
782
783
  }
@@ -1267,40 +1268,6 @@ function getLockKeyDir(key) {
1267
1268
  return lockDir + "/" + key;
1268
1269
  }
1269
1270
 
1270
- async function readLockPid(lockDir) {
1271
- try {
1272
- const files = await fp.readdir(lockDir);
1273
- const pidFile = files.find(name => name.startsWith('pid-'));
1274
- if (!pidFile) {
1275
- return null;
1276
- }
1277
- return pidFile.split("-")[1];
1278
- } catch (err) {
1279
- // 读取目录出错(可能不存在?)直接返回 null
1280
- return null;
1281
- }
1282
- }
1283
-
1284
- async function cleanFileLocks() {
1285
- let lockDir = getLockDir();
1286
- const files = await fp.readdir(lockDir);
1287
- for (let file of files) {
1288
- let lockKeyDir = lockDir + '/' + file;
1289
- let pid = await readLockPid(lockKeyDir);
1290
- if (!pid) {
1291
- continue;
1292
- }
1293
- if (!isPidAlive(pid)) {
1294
- // 持有锁的进程已经没了,则删除锁文件
1295
- try {
1296
- await fp.rm(lockKeyDir, { recursive: true, force: true });
1297
- } catch (err) {
1298
- console.$error(`cleanLock ${lockKeyDir} failed`, err);
1299
- }
1300
- }
1301
- }
1302
- }
1303
-
1304
1271
  async function _fileLock(key, fn) {
1305
1272
  `
1306
1273
  文件锁,返回true/false,
@@ -1310,41 +1277,28 @@ async function _fileLock(key, fn) {
1310
1277
  throw new Error('invalid arguments');
1311
1278
  }
1312
1279
 
1313
- let lockKeyDir = getLockKeyDir(key)
1314
- // 1. 尝试判断锁目录是否已存在
1315
- let lockExists = await fileExist(lockKeyDir);
1316
- if (lockExists) {
1317
- return false;
1318
- }
1319
-
1320
- // 2. 创建锁目录
1321
- try {
1322
- await fp.mkdir(lockKeyDir, { recursive: false });
1323
- } catch (err) {
1324
- // 如果 mkdir 依旧失败,说明在这段时间里又被别人抢先创建了
1325
- return false;
1326
- }
1280
+ let tempLockDir = getLockDir() + '/lock_' + uid()
1281
+ // 创建临时锁目录
1282
+ await fp.mkdir(tempLockDir, { recursive: false });
1327
1283
 
1328
1284
  const lockPidFile = `pid-${process.pid}`;
1329
- const lockPidFilePath = path.join(lockKeyDir, lockPidFile);
1285
+ const lockPidFilePath = path.join(tempLockDir, lockPidFile);
1286
+ await fp.writeFile(lockPidFilePath, '');
1287
+
1288
+ let lockKeyDir = getLockKeyDir(key)
1330
1289
  try {
1331
- await fp.writeFile(lockPidFilePath, '');
1290
+ await fp.rename(tempLockDir, lockKeyDir);
1332
1291
  } catch (err) {
1333
- // 无法写文件就释放锁并抛错
1334
- try {
1335
- await fp.rm(lockKeyDir, { recursive: true, force: true });
1336
- } catch (_) {}
1292
+ try { await fp.rm(tempLockDir, { recursive: true, force: true }); } catch (_) {}
1337
1293
  return false;
1338
1294
  }
1339
1295
 
1340
- // 4. 成功加锁后执行 fn
1296
+ // 成功加锁后执行 fn
1341
1297
  try {
1342
1298
  await fn();
1343
1299
  return true;
1344
1300
  } finally {
1345
- try {
1346
- await fp.rm(lockKeyDir, { recursive: true, force: true });
1347
- } catch (_) {}
1301
+ try { await fp.rm(lockKeyDir, { recursive: true, force: true }); } catch (_) {}
1348
1302
  }
1349
1303
  }
1350
1304
 
@@ -1388,21 +1342,20 @@ async function processLock(key, fn, wait = true) {
1388
1342
  }
1389
1343
  }
1390
1344
 
1391
- const $locks = {}
1392
1345
  async function _processLock(key, fn) {
1393
1346
  if (!key || typeof fn !== 'function') {
1394
1347
  throw new Error('invalid arguments');
1395
1348
  }
1396
1349
  key = key.trim();
1397
- if ($locks.hasOwnProperty(key)) {
1350
+ if (setting.locks.hasOwnProperty(key)) {
1398
1351
  return false;
1399
1352
  }
1400
- $locks[key] = true;
1353
+ setting.locks[key] = true;
1401
1354
  try {
1402
1355
  await fn();
1403
1356
  return true;
1404
1357
  } finally {
1405
- delete $locks[key];
1358
+ delete setting.locks[key];
1406
1359
  }
1407
1360
  }
1408
1361
 
@@ -1519,6 +1472,7 @@ async function cleanFile(path, maxChars = 9 * 1024 * 1024) {
1519
1472
  const bakFile = `${path}.bak`;
1520
1473
  // 备份日志
1521
1474
  try {
1475
+ await fp.rm(bakFile, { force: true });
1522
1476
  await fp.rename(path, bakFile);
1523
1477
  } catch (e) {
1524
1478
  console.$error(`Failed to rename ${path} -> ${bakFile}`, e);
@@ -1610,9 +1564,9 @@ function ei(cmd, args = [], shell = false) {
1610
1564
 
1611
1565
  async function eia(cmd, args = [], shell = false, input = null) {
1612
1566
  `
1613
- 当前进程不会卡住(事件循环仍在),但会 await 等子进程退出;
1614
- 输入输出默认由 cmd 进程持有;如果传 input,则把 input 写入 stdin。
1615
- `
1567
+ 当前进程不会卡住(事件循环仍在),但会 await 等子进程退出;
1568
+ 输入输出默认由 cmd 进程持有;如果传 input,则把 input 写入 stdin。
1569
+ `
1616
1570
  if (isRunningInBackground()) {
1617
1571
  throw 'Unsupported Operation';
1618
1572
  }
@@ -1643,18 +1597,51 @@ async function eia(cmd, args = [], shell = false, input = null) {
1643
1597
  });
1644
1598
  }
1645
1599
 
1646
- async function getCbText(mbNum) {
1647
- return await e(`pbpaste`, mbNum, false)
1600
+ /**
1601
+ * 获取剪贴板文本(macOS)
1602
+ * @returns {Promise<string|null>}
1603
+ */
1604
+ async function getCbText() {
1605
+ return new Promise((resolve) => {
1606
+ try {
1607
+ const p = spawn("pbpaste");
1608
+ let out = "";
1609
+
1610
+ p.stdout.setEncoding("utf8");
1611
+ p.stdout.on("data", (d) => out += d);
1612
+
1613
+ p.on("error", () => resolve(null));
1614
+ p.on("close", (code) => {
1615
+ if (code === 0) resolve(out);
1616
+ else resolve(null);
1617
+ });
1618
+ } catch (e) {
1619
+ resolve(null);
1620
+ }
1621
+ });
1648
1622
  }
1649
1623
 
1624
+ /**
1625
+ * 设置剪贴板文本(macOS)
1626
+ * @param {string} str
1627
+ * @returns {Promise<boolean>}
1628
+ */
1650
1629
  async function setCbText(str) {
1651
- let tempDir = getLibDataDir() + '/temp'
1652
- mkdir(tempDir)
1630
+ if (typeof str !== "string") return false;
1653
1631
 
1654
- let copyFile = tempDir + '/pbcopy.' + String(Math.random()).split('.')[1]
1655
- fs.writeFileSync(copyFile, str)
1656
- await e(`pbcopy < ${copyFile}`)
1657
- fs.unlinkSync(copyFile)
1632
+ return new Promise((resolve) => {
1633
+ try {
1634
+ const p = spawn("pbcopy");
1635
+
1636
+ p.on("error", () => resolve(false));
1637
+ p.on("close", (code) => resolve(code === 0));
1638
+
1639
+ p.stdin.setDefaultEncoding("utf8");
1640
+ p.stdin.end(str);
1641
+ } catch (e) {
1642
+ resolve(false);
1643
+ }
1644
+ });
1658
1645
  }
1659
1646
 
1660
1647
  async function sleep(milliseconds, tIds = []) {
@@ -2380,7 +2367,6 @@ module.exports = {
2380
2367
  hasTips,
2381
2368
  tipKeys,
2382
2369
  isPidAlive,
2383
- cleanFileLocks,
2384
2370
  getMd5Key,
2385
2371
  isMd5Key,
2386
2372
  terminalTitle,
@@ -2392,5 +2378,6 @@ module.exports = {
2392
2378
  currentRoom,
2393
2379
  currentRooms,
2394
2380
  uid,
2395
- consoleStrs
2381
+ consoleStrs,
2382
+ getLockDir
2396
2383
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsir",
3
- "version": "3.1.2",
3
+ "version": "3.1.3",
4
4
  "description": "JavaScript Script Management Tool",
5
5
  "main": "index.js",
6
6
  "scripts": {