most-box 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -13
- package/out/404/index.html +2 -2
- package/out/404.html +2 -2
- package/out/__next.__PAGE__.txt +6 -6
- package/out/__next._full.txt +15 -15
- package/out/__next._head.txt +3 -3
- package/out/__next._index.txt +7 -7
- package/out/__next._tree.txt +4 -4
- package/out/_next/static/chunks/00t5ddkkjek9v.js +1 -0
- package/out/_next/static/chunks/0hodisxqbtmye.js +1 -0
- package/out/_next/static/chunks/0jinrmgt26crz.js +1 -0
- package/out/_next/static/chunks/0mslhkdi-msxv.js +1 -0
- package/out/_next/static/chunks/1h-f81ddyfcmx.css +1 -0
- package/out/_next/static/chunks/1x76jmdus29em.js +1 -0
- package/out/_next/static/chunks/22vt64m0krmev.js +1 -0
- package/out/_next/static/chunks/{0vmjat-s4a3yj.css → 258tyt1xt34p4.css} +1 -1
- package/out/_next/static/chunks/266xuv6ts5889.js +5 -0
- package/out/_next/static/chunks/26hcvxjw9do9d.js +1 -0
- package/out/_next/static/chunks/{0nwdp-n94owg0.css → 2jnstwjg45vfp.css} +1 -1
- package/out/_next/static/chunks/2o-0n-9r5u1oh.js +1 -0
- package/out/_next/static/chunks/2pkm-6u9yq_cq.js +1 -0
- package/out/_next/static/chunks/31zdi2pl6em6-.css +1 -0
- package/out/_next/static/chunks/393isf5crmqic.js +1 -0
- package/out/_next/static/chunks/3e9ia0hs-pqvs.css +1 -0
- package/out/_next/static/chunks/3ej6im7cbakx3.js +1 -0
- package/out/_next/static/chunks/3ejz1mdljwwqf.css +1 -0
- package/out/_next/static/chunks/{0s7i7nfr9j3b1.js → 3m7_o1ihz8ay_.js} +1 -1
- package/out/_next/static/chunks/3qm2vbylm324o.css +1 -0
- package/out/_next/static/chunks/3uxltn4677hjy.js +1 -0
- package/out/_next/static/chunks/3w2qk10_udrd9.js +1 -0
- package/out/_next/static/chunks/3xl0fq2d3dle6.js +1 -0
- package/out/_next/static/chunks/{23kls47vn_rpg.css → 440es_b4nw-7c.css} +2 -2
- package/out/_next/static/chunks/{turbopack-026p0g9v9_pva.js → turbopack-3vvza9m6839dy.js} +1 -1
- package/out/_not-found/__next._full.txt +12 -12
- package/out/_not-found/__next._head.txt +3 -3
- package/out/_not-found/__next._index.txt +7 -7
- package/out/_not-found/__next._not-found.__PAGE__.txt +4 -4
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +2 -2
- package/out/_not-found/index.html +2 -2
- package/out/_not-found/index.txt +12 -12
- package/out/admin/__next._full.txt +13 -13
- package/out/admin/__next._head.txt +3 -3
- package/out/admin/__next._index.txt +7 -7
- package/out/admin/__next._tree.txt +2 -2
- package/out/admin/__next.admin.__PAGE__.txt +4 -4
- package/out/admin/__next.admin.txt +3 -3
- package/out/admin/index.html +2 -2
- package/out/admin/index.txt +13 -13
- package/out/app/__next._full.txt +13 -13
- package/out/app/__next._head.txt +3 -3
- package/out/app/__next._index.txt +7 -7
- package/out/app/__next._tree.txt +2 -2
- package/out/app/__next.app.__PAGE__.txt +4 -4
- package/out/app/__next.app.txt +3 -3
- package/out/app/index.html +2 -2
- package/out/app/index.txt +13 -13
- package/out/chat/__next._full.txt +14 -14
- package/out/chat/__next._head.txt +3 -3
- package/out/chat/__next._index.txt +7 -7
- package/out/chat/__next._tree.txt +3 -3
- package/out/chat/__next.chat.__PAGE__.txt +4 -4
- package/out/chat/__next.chat.txt +4 -4
- package/out/chat/index.html +2 -2
- package/out/chat/index.txt +14 -14
- package/out/chat/join/__next._full.txt +14 -14
- package/out/chat/join/__next._head.txt +3 -3
- package/out/chat/join/__next._index.txt +7 -7
- package/out/chat/join/__next._tree.txt +3 -3
- package/out/chat/join/__next.chat.join.__PAGE__.txt +4 -4
- package/out/chat/join/__next.chat.join.txt +3 -3
- package/out/chat/join/__next.chat.txt +4 -4
- package/out/chat/join/index.html +2 -2
- package/out/chat/join/index.txt +14 -14
- package/out/demo/__next._full.txt +17 -16
- package/out/demo/__next._head.txt +3 -3
- package/out/demo/__next._index.txt +7 -7
- package/out/demo/__next._tree.txt +6 -5
- package/out/demo/__next.demo.__PAGE__.txt +8 -7
- package/out/demo/__next.demo.txt +3 -3
- package/out/demo/index.html +3 -3
- package/out/demo/index.txt +17 -16
- package/out/download/__next._full.txt +37 -33
- package/out/download/__next._head.txt +3 -3
- package/out/download/__next._index.txt +7 -7
- package/out/download/__next._tree.txt +3 -3
- package/out/download/__next.download.__PAGE__.txt +6 -6
- package/out/download/__next.download.txt +3 -3
- package/out/download/index.html +2 -2
- package/out/download/index.txt +37 -33
- package/out/game/__next._full.txt +11 -11
- package/out/game/__next._head.txt +3 -3
- package/out/game/__next._index.txt +7 -7
- package/out/game/__next._tree.txt +2 -2
- package/out/game/__next.game.__PAGE__.txt +2 -2
- package/out/game/__next.game.txt +3 -3
- package/out/game/gandengyan/__next._full.txt +14 -14
- package/out/game/gandengyan/__next._head.txt +3 -3
- package/out/game/gandengyan/__next._index.txt +7 -7
- package/out/game/gandengyan/__next._tree.txt +3 -3
- package/out/game/gandengyan/__next.game.gandengyan.__PAGE__.txt +5 -5
- package/out/game/gandengyan/__next.game.gandengyan.txt +3 -3
- package/out/game/gandengyan/__next.game.txt +3 -3
- package/out/game/gandengyan/index.html +2 -2
- package/out/game/gandengyan/index.txt +14 -14
- package/out/game/index.html +1 -1
- package/out/game/index.txt +11 -11
- package/out/game/zhajinhua/__next._full.txt +14 -13
- package/out/game/zhajinhua/__next._head.txt +3 -3
- package/out/game/zhajinhua/__next._index.txt +7 -7
- package/out/game/zhajinhua/__next._tree.txt +3 -2
- package/out/game/zhajinhua/__next.game.txt +3 -3
- package/out/game/zhajinhua/__next.game.zhajinhua.__PAGE__.txt +5 -4
- package/out/game/zhajinhua/__next.game.zhajinhua.txt +3 -3
- package/out/game/zhajinhua/index.html +2 -2
- package/out/game/zhajinhua/index.txt +14 -13
- package/out/index.html +2 -2
- package/out/index.txt +15 -15
- package/out/note/__next._full.txt +13 -13
- package/out/note/__next._head.txt +3 -3
- package/out/note/__next._index.txt +7 -7
- package/out/note/__next._tree.txt +2 -2
- package/out/note/__next.note.__PAGE__.txt +4 -4
- package/out/note/__next.note.txt +3 -3
- package/out/note/index.html +2 -2
- package/out/note/index.txt +13 -13
- package/out/ping/__next._full.txt +14 -14
- package/out/ping/__next._head.txt +3 -3
- package/out/ping/__next._index.txt +7 -7
- package/out/ping/__next._tree.txt +3 -3
- package/out/ping/__next.ping.__PAGE__.txt +5 -5
- package/out/ping/__next.ping.txt +3 -3
- package/out/ping/index.html +2 -2
- package/out/ping/index.txt +14 -14
- package/out/web3/__next._full.txt +14 -14
- package/out/web3/__next._head.txt +3 -3
- package/out/web3/__next._index.txt +7 -7
- package/out/web3/__next._tree.txt +3 -3
- package/out/web3/__next.web3.__PAGE__.txt +4 -4
- package/out/web3/__next.web3.txt +4 -4
- package/out/web3/ed25519/__next._full.txt +12 -12
- package/out/web3/ed25519/__next._head.txt +3 -3
- package/out/web3/ed25519/__next._index.txt +7 -7
- package/out/web3/ed25519/__next._tree.txt +3 -3
- package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +2 -2
- package/out/web3/ed25519/__next.web3.ed25519.txt +3 -3
- package/out/web3/ed25519/__next.web3.txt +4 -4
- package/out/web3/ed25519/index.html +1 -1
- package/out/web3/ed25519/index.txt +12 -12
- package/out/web3/index.html +2 -2
- package/out/web3/index.txt +14 -14
- package/out/web3/tools/__next._full.txt +12 -12
- package/out/web3/tools/__next._head.txt +3 -3
- package/out/web3/tools/__next._index.txt +7 -7
- package/out/web3/tools/__next._tree.txt +3 -3
- package/out/web3/tools/__next.web3.tools.__PAGE__.txt +2 -2
- package/out/web3/tools/__next.web3.tools.txt +3 -3
- package/out/web3/tools/__next.web3.txt +4 -4
- package/out/web3/tools/index.html +1 -1
- package/out/web3/tools/index.txt +12 -12
- package/package.json +1 -1
- package/server/src/core/gameRoom.js +12 -0
- package/server/src/core/zhajinhua.js +82 -14
- package/server/src/games/gandengyan.js +39 -0
- package/server/src/http/app.js +66 -1
- package/server/src/http/nodeStatus.js +18 -0
- package/server/src/index.js +667 -165
- package/out/_next/static/chunks/0-ryc9icn6dql.css +0 -1
- package/out/_next/static/chunks/088-v8tzobg5g.js +0 -1
- package/out/_next/static/chunks/0g9303i8psy6z.js +0 -1
- package/out/_next/static/chunks/10p6-qnuk3p9x.js +0 -1
- package/out/_next/static/chunks/1ei8b9nw-ea8p.css +0 -1
- package/out/_next/static/chunks/2524ggtr3e4fp.js +0 -1
- package/out/_next/static/chunks/2fmkcw8ui5xp3.js +0 -5
- package/out/_next/static/chunks/2mcmt4yla_xle.js +0 -1
- package/out/_next/static/chunks/2nbju8t9-ux58.css +0 -1
- package/out/_next/static/chunks/2v7_4ps1l4a8o.css +0 -1
- package/out/_next/static/chunks/2vk2ln79gpvm4.js +0 -1
- package/out/_next/static/chunks/37msy2-4ucgts.js +0 -1
- package/out/_next/static/chunks/384w3w4qvhw61.js +0 -1
- package/out/_next/static/chunks/3aa-weedhv4e0.js +0 -1
- package/out/_next/static/chunks/3erbaukgkw8f2.js +0 -1
- package/out/_next/static/chunks/3fea100s764m-.js +0 -1
- package/out/_next/static/chunks/3qj5xz_d4cbk6.js +0 -1
- /package/out/_next/static/{NPIlpdzQWkiVZpgfhlypH → TXy93SnAZQ7OQ-aVUkmHa}/_buildManifest.js +0 -0
- /package/out/_next/static/{NPIlpdzQWkiVZpgfhlypH → TXy93SnAZQ7OQ-aVUkmHa}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{NPIlpdzQWkiVZpgfhlypH → TXy93SnAZQ7OQ-aVUkmHa}/_ssgManifest.js +0 -0
package/server/src/index.js
CHANGED
|
@@ -67,12 +67,45 @@ import {
|
|
|
67
67
|
|
|
68
68
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
69
69
|
const CHAT_FILE_ROOT = 'chat-file'
|
|
70
|
+
const TRANSIENT_CHANNEL_TYPES = new Set(['game'])
|
|
71
|
+
const DEFAULT_OWNER_BUCKET = '__local__'
|
|
72
|
+
const USER_DATA_SCHEMA_VERSION = 1
|
|
73
|
+
const IMPORT_CHECK_TTL_MS = 10 * 60 * 1000
|
|
74
|
+
const IMPORT_CHECK_MAX_FILES = 5000
|
|
70
75
|
|
|
71
76
|
function normalizeOwnerAddress(address) {
|
|
72
77
|
const value = String(address || '').trim()
|
|
73
78
|
return /^0x[a-fA-F0-9]{40}$/.test(value) ? value.toLowerCase() : ''
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
function getOwnerBucketKey(address) {
|
|
82
|
+
return normalizeOwnerAddress(address) || DEFAULT_OWNER_BUCKET
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeMetadataBuckets(input) {
|
|
86
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
87
|
+
return {}
|
|
88
|
+
}
|
|
89
|
+
const buckets = {}
|
|
90
|
+
for (const [rawOwner, records] of Object.entries(input)) {
|
|
91
|
+
const ownerKey =
|
|
92
|
+
rawOwner === DEFAULT_OWNER_BUCKET
|
|
93
|
+
? DEFAULT_OWNER_BUCKET
|
|
94
|
+
: normalizeOwnerAddress(rawOwner)
|
|
95
|
+
if (!ownerKey || !Array.isArray(records)) continue
|
|
96
|
+
buckets[ownerKey] = records.map(record => ({ ...record }))
|
|
97
|
+
}
|
|
98
|
+
return buckets
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function cloneMetadataRecord(record, ownerAddress = '') {
|
|
102
|
+
return {
|
|
103
|
+
...record,
|
|
104
|
+
ownerAddress:
|
|
105
|
+
ownerAddress && ownerAddress !== DEFAULT_OWNER_BUCKET ? ownerAddress : '',
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
76
109
|
function getPathBaseName(fileName) {
|
|
77
110
|
const parts = String(fileName || '').split('/').filter(Boolean)
|
|
78
111
|
return parts[parts.length - 1] || 'unnamed_file'
|
|
@@ -122,12 +155,13 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
122
155
|
#store = null
|
|
123
156
|
#swarm = null
|
|
124
157
|
#drives = new Map()
|
|
125
|
-
#publishedFiles =
|
|
158
|
+
#publishedFiles = {}
|
|
126
159
|
#holdings = []
|
|
127
|
-
#trashFiles =
|
|
160
|
+
#trashFiles = {}
|
|
128
161
|
#initialized = false
|
|
129
162
|
#options = null
|
|
130
163
|
#activeDownloads = new Map()
|
|
164
|
+
#importChecks = new Map()
|
|
131
165
|
#drivePromises = new Map()
|
|
132
166
|
#fileDiscoveries = new Map()
|
|
133
167
|
#fileMonitors = new Map()
|
|
@@ -302,7 +336,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
302
336
|
|
|
303
337
|
this.#publishedFiles = this.#loadPublishedMetadata()
|
|
304
338
|
console.log(
|
|
305
|
-
`[MostBox] Loaded ${this.#publishedFiles
|
|
339
|
+
`[MostBox] Loaded ${this.#countBucketRecords(this.#publishedFiles)} published files`
|
|
306
340
|
)
|
|
307
341
|
|
|
308
342
|
this.#holdings = this.#loadHoldingsMetadata()
|
|
@@ -317,7 +351,9 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
317
351
|
}
|
|
318
352
|
|
|
319
353
|
this.#trashFiles = this.#loadTrashMetadata()
|
|
320
|
-
console.log(
|
|
354
|
+
console.log(
|
|
355
|
+
`[MostBox] Loaded ${this.#countBucketRecords(this.#trashFiles)} trash files`
|
|
356
|
+
)
|
|
321
357
|
|
|
322
358
|
this.#channels = this.#loadChannelsMetadata()
|
|
323
359
|
console.log(`[MostBox] Loaded ${this.#channels.length} channels`)
|
|
@@ -428,6 +464,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
428
464
|
this.#channelChatDiscoveries.clear()
|
|
429
465
|
this.#channelPeers.clear()
|
|
430
466
|
this.#channels = []
|
|
467
|
+
this.#importChecks.clear()
|
|
431
468
|
|
|
432
469
|
if (this.#store) {
|
|
433
470
|
await this.#store.close()
|
|
@@ -470,7 +507,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
470
507
|
* @param {string|Buffer} content - 文件路径(字符串)或内容(Buffer)
|
|
471
508
|
* @param {string} [fileName] - 文件名(Buffer 输入时必填)
|
|
472
509
|
* @param {object} [options] - 发布选项
|
|
473
|
-
* @param {string|null} [options.localPath] - 持有记录中的本地路径
|
|
474
510
|
* @returns {Promise<{ cid: string, link: string, fileName: string }>}
|
|
475
511
|
*/
|
|
476
512
|
async publishFile(content, fileName, options = {}) {
|
|
@@ -525,15 +561,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
525
561
|
const { cid: rootCid } = await calculateCid(content)
|
|
526
562
|
const cidString = rootCid.toString()
|
|
527
563
|
const { driveName: name } = this.#getCidInfo(cidString)
|
|
528
|
-
const
|
|
529
|
-
options.localPath === undefined ? cleanPath : options.localPath
|
|
530
|
-
|
|
564
|
+
const publishedBucket = this.#getPublishedBucket(ownerAddress, true)
|
|
531
565
|
// 检查相同内容是否已存在
|
|
532
|
-
const existingIndex =
|
|
533
|
-
f => f.cid === cidString && this.#recordMatchesOwner(f, ownerAddress)
|
|
534
|
-
)
|
|
566
|
+
const existingIndex = publishedBucket.findIndex(f => f.cid === cidString)
|
|
535
567
|
if (existingIndex !== -1) {
|
|
536
|
-
const existing =
|
|
568
|
+
const existing = publishedBucket[existingIndex]
|
|
537
569
|
await this.#joinCidTopicInternal(cidString, {
|
|
538
570
|
server: true,
|
|
539
571
|
client: false,
|
|
@@ -542,7 +574,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
542
574
|
cid: cidString,
|
|
543
575
|
fileName: existing.fileName,
|
|
544
576
|
size: fileSize,
|
|
545
|
-
localPath: holdingLocalPath,
|
|
546
577
|
driveName: name,
|
|
547
578
|
source: 'published',
|
|
548
579
|
})
|
|
@@ -613,20 +644,18 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
613
644
|
}
|
|
614
645
|
|
|
615
646
|
// 存储 displayName(用户看到的文件夹路径),不存储 drivePath
|
|
616
|
-
|
|
647
|
+
publishedBucket.push({
|
|
617
648
|
fileName: safeFileName,
|
|
618
649
|
cid: cidString,
|
|
619
650
|
driveName: name,
|
|
620
651
|
publishedAt: new Date().toISOString(),
|
|
621
652
|
starred: false,
|
|
622
|
-
ownerAddress,
|
|
623
653
|
})
|
|
624
654
|
this.#savePublishedMetadata()
|
|
625
655
|
this.#upsertHolding({
|
|
626
656
|
cid: cidString,
|
|
627
657
|
fileName: safeFileName,
|
|
628
658
|
size: fileSize,
|
|
629
|
-
localPath: holdingLocalPath,
|
|
630
659
|
driveName: name,
|
|
631
660
|
source: 'published',
|
|
632
661
|
})
|
|
@@ -700,8 +729,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
700
729
|
size:
|
|
701
730
|
existingHolding?.size ??
|
|
702
731
|
(Number.isFinite(localContent.size) ? localContent.size : 0),
|
|
703
|
-
localPath:
|
|
704
|
-
existingHolding?.localPath || existingFile?.localPath || null,
|
|
705
732
|
driveName: existingFile?.driveName || name,
|
|
706
733
|
source: existingHolding?.source || 'published',
|
|
707
734
|
})
|
|
@@ -959,27 +986,26 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
959
986
|
savedPath: savePath,
|
|
960
987
|
}
|
|
961
988
|
|
|
962
|
-
|
|
963
|
-
const existingIndex =
|
|
964
|
-
f => f.cid === cidString
|
|
989
|
+
const publishedBucket = this.#getPublishedBucket(ownerAddress, true)
|
|
990
|
+
const existingIndex = publishedBucket.findIndex(
|
|
991
|
+
f => f.cid === cidString
|
|
965
992
|
)
|
|
966
993
|
this.#assertDisplayNameAvailable(sanitizedFileName, {
|
|
967
994
|
ownerAddress,
|
|
968
995
|
excludeCid: cidString,
|
|
969
996
|
})
|
|
970
997
|
if (existingIndex !== -1) {
|
|
971
|
-
const existing =
|
|
998
|
+
const existing = publishedBucket[existingIndex]
|
|
972
999
|
existing.fileName = sanitizedFileName
|
|
973
1000
|
existing.driveName = name
|
|
974
1001
|
existing.publishedAt = new Date().toISOString()
|
|
975
1002
|
} else {
|
|
976
|
-
|
|
1003
|
+
publishedBucket.push({
|
|
977
1004
|
fileName: sanitizedFileName,
|
|
978
1005
|
cid: cidString,
|
|
979
1006
|
driveName: name,
|
|
980
1007
|
publishedAt: new Date().toISOString(),
|
|
981
1008
|
starred: false,
|
|
982
|
-
ownerAddress,
|
|
983
1009
|
})
|
|
984
1010
|
}
|
|
985
1011
|
this.#savePublishedMetadata()
|
|
@@ -988,7 +1014,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
988
1014
|
cid: cidString,
|
|
989
1015
|
fileName: sanitizedFileName,
|
|
990
1016
|
size: savedSize,
|
|
991
|
-
localPath: savePath,
|
|
992
1017
|
driveName: name,
|
|
993
1018
|
source: 'downloaded',
|
|
994
1019
|
})
|
|
@@ -1118,12 +1143,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1118
1143
|
*/
|
|
1119
1144
|
listPublishedFiles(options = {}) {
|
|
1120
1145
|
this.#ensureInitialized()
|
|
1121
|
-
let files = this.#publishedFiles
|
|
1122
1146
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1123
|
-
|
|
1124
|
-
if (ownerAddress) {
|
|
1125
|
-
files = files.filter(f => this.#recordMatchesOwner(f, ownerAddress))
|
|
1126
|
-
}
|
|
1147
|
+
let files = this.#getPublishedBucket(ownerAddress)
|
|
1127
1148
|
|
|
1128
1149
|
if (options.starred === true) {
|
|
1129
1150
|
files = files.filter(f => f.starred === true)
|
|
@@ -1135,7 +1156,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1135
1156
|
link: `most://${f.cid}?filename=${encodeURIComponent(f.fileName)}`,
|
|
1136
1157
|
publishedAt: f.publishedAt,
|
|
1137
1158
|
starred: f.starred || false,
|
|
1138
|
-
ownerAddress:
|
|
1159
|
+
ownerAddress: ownerAddress || '',
|
|
1139
1160
|
}))
|
|
1140
1161
|
}
|
|
1141
1162
|
|
|
@@ -1147,17 +1168,16 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1147
1168
|
toggleStarred(cid, options = {}) {
|
|
1148
1169
|
this.#ensureInitialized()
|
|
1149
1170
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
)
|
|
1171
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
1172
|
+
const index = files.findIndex(f => f.cid === cid)
|
|
1153
1173
|
if (index === -1) {
|
|
1154
1174
|
throw new Error('File not found')
|
|
1155
1175
|
}
|
|
1156
|
-
|
|
1176
|
+
files[index].starred = !files[index].starred
|
|
1157
1177
|
this.#savePublishedMetadata()
|
|
1158
1178
|
return {
|
|
1159
1179
|
cid,
|
|
1160
|
-
starred:
|
|
1180
|
+
starred: files[index].starred,
|
|
1161
1181
|
}
|
|
1162
1182
|
}
|
|
1163
1183
|
|
|
@@ -1169,29 +1189,28 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1169
1189
|
async deletePublishedFile(cid, options = {}) {
|
|
1170
1190
|
this.#ensureInitialized()
|
|
1171
1191
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1174
|
-
)
|
|
1192
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
1193
|
+
const trashFiles = this.#getTrashBucket(ownerAddress, true)
|
|
1194
|
+
const index = files.findIndex(f => f.cid === cid)
|
|
1175
1195
|
if (index !== -1) {
|
|
1176
|
-
const fileRecord =
|
|
1196
|
+
const fileRecord = files[index]
|
|
1177
1197
|
const holding = this.#holdings.find(item => item.cid === fileRecord.cid)
|
|
1178
1198
|
|
|
1179
|
-
|
|
1199
|
+
trashFiles.push({
|
|
1180
1200
|
fileName: fileRecord.fileName,
|
|
1181
1201
|
cid: fileRecord.cid,
|
|
1182
1202
|
driveName:
|
|
1183
1203
|
fileRecord.driveName || this.#getCidInfo(fileRecord.cid).driveName,
|
|
1184
1204
|
size: holding?.size ?? fileRecord.size ?? 0,
|
|
1185
|
-
localPath: holding?.localPath || fileRecord.localPath || null,
|
|
1186
1205
|
source: holding?.source || 'published',
|
|
1187
1206
|
publishedAt: fileRecord.publishedAt,
|
|
1188
1207
|
starred: fileRecord.starred || false,
|
|
1189
|
-
ownerAddress: fileRecord.ownerAddress || ownerAddress,
|
|
1190
1208
|
deletedAt: new Date().toISOString(),
|
|
1191
1209
|
})
|
|
1192
1210
|
this.#saveTrashMetadata()
|
|
1193
1211
|
|
|
1194
|
-
|
|
1212
|
+
files.splice(index, 1)
|
|
1213
|
+
this.#setPublishedBucket(ownerAddress, files)
|
|
1195
1214
|
this.#savePublishedMetadata()
|
|
1196
1215
|
|
|
1197
1216
|
if (!this.#hasPublishedReference(fileRecord.cid)) {
|
|
@@ -1212,16 +1231,14 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1212
1231
|
listTrashFiles(options = {}) {
|
|
1213
1232
|
this.#ensureInitialized()
|
|
1214
1233
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1215
|
-
const files = ownerAddress
|
|
1216
|
-
? this.#trashFiles.filter(f => this.#recordMatchesOwner(f, ownerAddress))
|
|
1217
|
-
: this.#trashFiles
|
|
1234
|
+
const files = this.#getTrashBucket(ownerAddress)
|
|
1218
1235
|
return files.map(f => ({
|
|
1219
1236
|
fileName: f.fileName,
|
|
1220
1237
|
cid: f.cid,
|
|
1221
1238
|
link: `most://${f.cid}?filename=${encodeURIComponent(f.fileName)}`,
|
|
1222
1239
|
publishedAt: f.publishedAt,
|
|
1223
1240
|
starred: f.starred || false,
|
|
1224
|
-
ownerAddress:
|
|
1241
|
+
ownerAddress: ownerAddress || '',
|
|
1225
1242
|
deletedAt: f.deletedAt,
|
|
1226
1243
|
}))
|
|
1227
1244
|
}
|
|
@@ -1234,22 +1251,23 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1234
1251
|
async restoreTrashFile(cid, options = {}) {
|
|
1235
1252
|
this.#ensureInitialized()
|
|
1236
1253
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
)
|
|
1254
|
+
const trashFiles = this.#getTrashBucket(ownerAddress)
|
|
1255
|
+
const publishedFiles = this.#getPublishedBucket(ownerAddress, true)
|
|
1256
|
+
const index = trashFiles.findIndex(f => f.cid === cid)
|
|
1240
1257
|
if (index === -1) {
|
|
1241
1258
|
throw new Error('File not found in trash')
|
|
1242
1259
|
}
|
|
1243
1260
|
|
|
1244
|
-
const fileRecord =
|
|
1261
|
+
const fileRecord = trashFiles[index]
|
|
1245
1262
|
|
|
1246
1263
|
const { driveName } = this.#getCidInfo(fileRecord.cid)
|
|
1247
1264
|
|
|
1248
|
-
const existingIndex =
|
|
1249
|
-
f => f.cid === fileRecord.cid
|
|
1265
|
+
const existingIndex = publishedFiles.findIndex(
|
|
1266
|
+
f => f.cid === fileRecord.cid
|
|
1250
1267
|
)
|
|
1251
1268
|
if (existingIndex !== -1) {
|
|
1252
|
-
|
|
1269
|
+
trashFiles.splice(index, 1)
|
|
1270
|
+
this.#setTrashBucket(ownerAddress, trashFiles)
|
|
1253
1271
|
this.#saveTrashMetadata()
|
|
1254
1272
|
return this.listPublishedFiles({ ownerAddress })
|
|
1255
1273
|
}
|
|
@@ -1259,17 +1277,17 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1259
1277
|
excludeCid: fileRecord.cid,
|
|
1260
1278
|
})
|
|
1261
1279
|
|
|
1262
|
-
|
|
1280
|
+
publishedFiles.push({
|
|
1263
1281
|
fileName: fileRecord.fileName,
|
|
1264
1282
|
cid: fileRecord.cid,
|
|
1265
1283
|
driveName,
|
|
1266
1284
|
publishedAt: fileRecord.publishedAt,
|
|
1267
1285
|
starred: fileRecord.starred || false,
|
|
1268
|
-
ownerAddress: fileRecord.ownerAddress || ownerAddress,
|
|
1269
1286
|
})
|
|
1270
1287
|
this.#savePublishedMetadata()
|
|
1271
1288
|
|
|
1272
|
-
|
|
1289
|
+
trashFiles.splice(index, 1)
|
|
1290
|
+
this.#setTrashBucket(ownerAddress, trashFiles)
|
|
1273
1291
|
this.#saveTrashMetadata()
|
|
1274
1292
|
|
|
1275
1293
|
await this.#joinCidTopicInternal(fileRecord.cid, {
|
|
@@ -1280,7 +1298,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1280
1298
|
cid: fileRecord.cid,
|
|
1281
1299
|
fileName: fileRecord.fileName,
|
|
1282
1300
|
size: Number(fileRecord.size) || 0,
|
|
1283
|
-
localPath: fileRecord.localPath || null,
|
|
1284
1301
|
driveName,
|
|
1285
1302
|
source: fileRecord.source || 'published',
|
|
1286
1303
|
})
|
|
@@ -1296,15 +1313,15 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1296
1313
|
async permanentDeleteTrashFile(cid, options = {}) {
|
|
1297
1314
|
this.#ensureInitialized()
|
|
1298
1315
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
)
|
|
1316
|
+
const trashFiles = this.#getTrashBucket(ownerAddress)
|
|
1317
|
+
const index = trashFiles.findIndex(f => f.cid === cid)
|
|
1302
1318
|
if (index !== -1) {
|
|
1303
|
-
const fileRecord =
|
|
1319
|
+
const fileRecord = trashFiles[index]
|
|
1304
1320
|
const driveName =
|
|
1305
1321
|
fileRecord.driveName || this.#getCidInfo(fileRecord.cid).driveName
|
|
1306
1322
|
|
|
1307
|
-
|
|
1323
|
+
trashFiles.splice(index, 1)
|
|
1324
|
+
this.#setTrashBucket(ownerAddress, trashFiles)
|
|
1308
1325
|
this.#saveTrashMetadata()
|
|
1309
1326
|
|
|
1310
1327
|
if (!this.#hasAnyUserReference(fileRecord.cid)) {
|
|
@@ -1329,18 +1346,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1329
1346
|
async emptyTrash(options = {}) {
|
|
1330
1347
|
this.#ensureInitialized()
|
|
1331
1348
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
for (const fileRecord of this.#trashFiles) {
|
|
1336
|
-
if (ownerAddress && !this.#recordMatchesOwner(fileRecord, ownerAddress)) {
|
|
1337
|
-
remainingTrash.push(fileRecord)
|
|
1338
|
-
continue
|
|
1339
|
-
}
|
|
1340
|
-
removedTrash.push(fileRecord)
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
this.#trashFiles = remainingTrash
|
|
1349
|
+
const removedTrash = [...this.#getTrashBucket(ownerAddress)]
|
|
1350
|
+
this.#setTrashBucket(ownerAddress, [])
|
|
1344
1351
|
this.#saveTrashMetadata()
|
|
1345
1352
|
|
|
1346
1353
|
for (const fileRecord of removedTrash) {
|
|
@@ -1419,15 +1426,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1419
1426
|
used: usedSize,
|
|
1420
1427
|
free: freeSize,
|
|
1421
1428
|
fileCount: ownerAddress
|
|
1422
|
-
? this.#
|
|
1423
|
-
|
|
1424
|
-
).length
|
|
1425
|
-
: this.#publishedFiles.length,
|
|
1429
|
+
? this.#getPublishedBucket(ownerAddress).length
|
|
1430
|
+
: this.#countBucketRecords(this.#publishedFiles),
|
|
1426
1431
|
trashCount: ownerAddress
|
|
1427
|
-
? this.#
|
|
1428
|
-
|
|
1429
|
-
).length
|
|
1430
|
-
: this.#trashFiles.length,
|
|
1432
|
+
? this.#getTrashBucket(ownerAddress).length
|
|
1433
|
+
: this.#countBucketRecords(this.#trashFiles),
|
|
1431
1434
|
}
|
|
1432
1435
|
}
|
|
1433
1436
|
|
|
@@ -1441,9 +1444,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1441
1444
|
moveFile(cid, newFileName, options = {}) {
|
|
1442
1445
|
this.#ensureInitialized()
|
|
1443
1446
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
)
|
|
1447
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
1448
|
+
const index = files.findIndex(f => f.cid === cid)
|
|
1447
1449
|
if (index === -1) {
|
|
1448
1450
|
throw new Error('File not found')
|
|
1449
1451
|
}
|
|
@@ -1452,8 +1454,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1452
1454
|
ownerAddress,
|
|
1453
1455
|
excludeCid: cid,
|
|
1454
1456
|
})
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
+
files[index].fileName = safeFileName
|
|
1458
|
+
files[index].publishedAt = new Date().toISOString()
|
|
1457
1459
|
this.#savePublishedMetadata()
|
|
1458
1460
|
return {
|
|
1459
1461
|
cid,
|
|
@@ -1474,12 +1476,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1474
1476
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1475
1477
|
const prefix = oldPath + '/'
|
|
1476
1478
|
const updates = []
|
|
1479
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
1477
1480
|
|
|
1478
|
-
for (const file of
|
|
1479
|
-
if (
|
|
1480
|
-
file.fileName.startsWith(prefix) &&
|
|
1481
|
-
this.#recordMatchesOwner(file, ownerAddress)
|
|
1482
|
-
) {
|
|
1481
|
+
for (const file of files) {
|
|
1482
|
+
if (file.fileName.startsWith(prefix)) {
|
|
1483
1483
|
const remainder = file.fileName.substring(prefix.length)
|
|
1484
1484
|
const newFileName = sanitizeFilename(
|
|
1485
1485
|
remainder ? newPath + '/' + remainder : newPath
|
|
@@ -1544,10 +1544,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1544
1544
|
getPublishedFiles(options = {}) {
|
|
1545
1545
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1546
1546
|
return ownerAddress
|
|
1547
|
-
? this.#
|
|
1548
|
-
|
|
1549
|
-
)
|
|
1550
|
-
: this.#publishedFiles
|
|
1547
|
+
? this.#getPublishedBucket(ownerAddress)
|
|
1548
|
+
: this.#allPublishedRecords()
|
|
1551
1549
|
}
|
|
1552
1550
|
|
|
1553
1551
|
listUsers() {
|
|
@@ -1568,17 +1566,21 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1568
1566
|
return users.get(ownerAddress)
|
|
1569
1567
|
}
|
|
1570
1568
|
|
|
1571
|
-
for (const
|
|
1572
|
-
const entry = ensure(
|
|
1569
|
+
for (const [ownerAddress, files] of Object.entries(this.#publishedFiles)) {
|
|
1570
|
+
const entry = ensure(ownerAddress)
|
|
1573
1571
|
if (!entry) continue
|
|
1574
|
-
entry.fileCount +=
|
|
1575
|
-
|
|
1572
|
+
entry.fileCount += files.length
|
|
1573
|
+
for (const file of files) {
|
|
1574
|
+
entry.cids.add(file.cid)
|
|
1575
|
+
}
|
|
1576
1576
|
}
|
|
1577
|
-
for (const
|
|
1578
|
-
const entry = ensure(
|
|
1577
|
+
for (const [ownerAddress, files] of Object.entries(this.#trashFiles)) {
|
|
1578
|
+
const entry = ensure(ownerAddress)
|
|
1579
1579
|
if (!entry) continue
|
|
1580
|
-
entry.trashCount +=
|
|
1581
|
-
|
|
1580
|
+
entry.trashCount += files.length
|
|
1581
|
+
for (const file of files) {
|
|
1582
|
+
entry.cids.add(file.cid)
|
|
1583
|
+
}
|
|
1582
1584
|
}
|
|
1583
1585
|
|
|
1584
1586
|
return [...users.values()].map(user => ({
|
|
@@ -1596,58 +1598,258 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1596
1598
|
throw new ValidationError('valid owner address is required')
|
|
1597
1599
|
}
|
|
1598
1600
|
|
|
1599
|
-
const
|
|
1600
|
-
const beforeFiles = this.#publishedFiles.length
|
|
1601
|
-
const beforeTrash = this.#trashFiles.length
|
|
1601
|
+
const result = await this.#clearUserDataInternal(ownerAddress)
|
|
1602
1602
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
this.#
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1603
|
+
return {
|
|
1604
|
+
ownerAddress,
|
|
1605
|
+
...result,
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
exportUserMetadata(ownerAddressInput) {
|
|
1610
|
+
this.#ensureInitialized()
|
|
1611
|
+
const ownerAddress = normalizeOwnerAddress(ownerAddressInput)
|
|
1612
|
+
if (!ownerAddress) {
|
|
1613
|
+
throw new ValidationError('valid owner address is required')
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
const files = this.#getPublishedBucket(ownerAddress).map(file => ({
|
|
1617
|
+
fileName: file.fileName,
|
|
1618
|
+
cid: file.cid,
|
|
1619
|
+
driveName: file.driveName || this.#getCidInfo(file.cid).driveName,
|
|
1620
|
+
publishedAt: file.publishedAt,
|
|
1621
|
+
starred: file.starred || false,
|
|
1622
|
+
link: buildMostLink(file.cid, file.fileName),
|
|
1623
|
+
}))
|
|
1624
|
+
const trashFiles = this.#getTrashBucket(ownerAddress).map(file => ({
|
|
1625
|
+
fileName: file.fileName,
|
|
1626
|
+
cid: file.cid,
|
|
1627
|
+
driveName: file.driveName || this.#getCidInfo(file.cid).driveName,
|
|
1628
|
+
size: Number(file.size) || 0,
|
|
1629
|
+
source: file.source || 'published',
|
|
1630
|
+
publishedAt: file.publishedAt,
|
|
1631
|
+
starred: file.starred || false,
|
|
1632
|
+
deletedAt: file.deletedAt,
|
|
1633
|
+
link: buildMostLink(file.cid, file.fileName),
|
|
1634
|
+
}))
|
|
1635
|
+
const channels = this.#channels
|
|
1636
|
+
.filter(channel => this.#channelHasMember(channel, ownerAddress))
|
|
1618
1637
|
.map(channel => ({
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1638
|
+
name: channel.name,
|
|
1639
|
+
type: channel.type,
|
|
1640
|
+
coreKey: channel.coreKey,
|
|
1641
|
+
createdAt: channel.createdAt,
|
|
1642
|
+
lastMessageAt: channel.lastMessageAt || '',
|
|
1643
|
+
member: this.#getChannelMembers(channel).find(
|
|
1644
|
+
member => member.address === ownerAddress
|
|
1645
|
+
),
|
|
1646
|
+
remark: channel.remarks?.[ownerAddress] || '',
|
|
1647
|
+
pinned: Boolean(channel.pinnedBy?.[ownerAddress]),
|
|
1625
1648
|
}))
|
|
1626
|
-
.filter(channel => channel.members.length > 0)
|
|
1627
1649
|
|
|
1650
|
+
return {
|
|
1651
|
+
schemaVersion: USER_DATA_SCHEMA_VERSION,
|
|
1652
|
+
exportedAt: new Date().toISOString(),
|
|
1653
|
+
ownerAddress,
|
|
1654
|
+
files,
|
|
1655
|
+
trashFiles,
|
|
1656
|
+
channels,
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
async checkUserImport(input = {}, options = {}) {
|
|
1661
|
+
this.#ensureInitialized()
|
|
1662
|
+
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1663
|
+
if (!ownerAddress) {
|
|
1664
|
+
throw new ValidationError('valid owner address is required')
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const normalized = this.#normalizeUserImportPackage(input)
|
|
1668
|
+
const failures = []
|
|
1669
|
+
const seenPaths = new Set()
|
|
1670
|
+
const checkedActiveCids = new Set()
|
|
1671
|
+
const importedCids = new Set([
|
|
1672
|
+
...normalized.files.map(file => file.cid),
|
|
1673
|
+
...normalized.trashFiles.map(file => file.cid),
|
|
1674
|
+
])
|
|
1675
|
+
const currentCids = this.#collectUserCids(ownerAddress)
|
|
1676
|
+
let requiredBytes = 0
|
|
1677
|
+
|
|
1678
|
+
if (normalized.files.length > IMPORT_CHECK_MAX_FILES) {
|
|
1679
|
+
failures.push({
|
|
1680
|
+
scope: 'package',
|
|
1681
|
+
error: `too many files: max ${IMPORT_CHECK_MAX_FILES}`,
|
|
1682
|
+
})
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
for (const file of normalized.files) {
|
|
1686
|
+
const pathKey = sanitizeFilename(file.fileName).toLowerCase()
|
|
1687
|
+
if (seenPaths.has(pathKey)) {
|
|
1688
|
+
failures.push({
|
|
1689
|
+
cid: file.cid,
|
|
1690
|
+
fileName: file.fileName,
|
|
1691
|
+
error: 'duplicate file path',
|
|
1692
|
+
})
|
|
1693
|
+
continue
|
|
1694
|
+
}
|
|
1695
|
+
seenPaths.add(pathKey)
|
|
1696
|
+
|
|
1697
|
+
const link = buildMostLink(file.cid, file.fileName)
|
|
1698
|
+
try {
|
|
1699
|
+
const result = await this.checkDownloadAvailability(link, {
|
|
1700
|
+
ownerAddress,
|
|
1701
|
+
timeout: options.timeout || DRIVE_ENTRY_TIMEOUT,
|
|
1702
|
+
})
|
|
1703
|
+
if (!checkedActiveCids.has(file.cid) && !result.alreadyExists) {
|
|
1704
|
+
requiredBytes += Number(result.size) || Number(file.size) || 0
|
|
1705
|
+
}
|
|
1706
|
+
checkedActiveCids.add(file.cid)
|
|
1707
|
+
} catch (err) {
|
|
1708
|
+
failures.push({
|
|
1709
|
+
cid: file.cid,
|
|
1710
|
+
fileName: file.fileName,
|
|
1711
|
+
error: err.message,
|
|
1712
|
+
code: err.code || 'UNAVAILABLE',
|
|
1713
|
+
})
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
let reclaimableBytes = 0
|
|
1718
|
+
for (const cid of currentCids) {
|
|
1719
|
+
if (importedCids.has(cid)) continue
|
|
1720
|
+
const referencedByOtherUser =
|
|
1721
|
+
this.#allPublishedRecords().some(
|
|
1722
|
+
file => file.cid === cid && file.ownerAddress !== ownerAddress
|
|
1723
|
+
) ||
|
|
1724
|
+
this.#allTrashRecords().some(
|
|
1725
|
+
file => file.cid === cid && file.ownerAddress !== ownerAddress
|
|
1726
|
+
)
|
|
1727
|
+
if (referencedByOtherUser) continue
|
|
1728
|
+
const holding = this.#holdings.find(item => item.cid === cid)
|
|
1729
|
+
reclaimableBytes += Number(holding?.size) || 0
|
|
1730
|
+
}
|
|
1731
|
+
const availableBytes = Math.max(
|
|
1732
|
+
0,
|
|
1733
|
+
this.#options.capacityBytes - this.#getUsedBytes() + reclaimableBytes
|
|
1734
|
+
)
|
|
1735
|
+
if (requiredBytes > availableBytes) {
|
|
1736
|
+
failures.push({
|
|
1737
|
+
scope: 'capacity',
|
|
1738
|
+
error: 'insufficient capacity',
|
|
1739
|
+
requiredBytes,
|
|
1740
|
+
availableBytes,
|
|
1741
|
+
})
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
const ready = failures.length === 0
|
|
1745
|
+
const checkId = `import_${Date.now()}_${crypto.randomBytes(6).toString('hex')}`
|
|
1746
|
+
if (ready) {
|
|
1747
|
+
this.#importChecks.set(checkId, {
|
|
1748
|
+
ownerAddress,
|
|
1749
|
+
packageHash: this.#hashImportPackage(input),
|
|
1750
|
+
expiresAt: Date.now() + IMPORT_CHECK_TTL_MS,
|
|
1751
|
+
})
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
return {
|
|
1755
|
+
ready,
|
|
1756
|
+
checkId: ready ? checkId : '',
|
|
1757
|
+
failures,
|
|
1758
|
+
currentFileCount: this.#getPublishedBucket(ownerAddress).length,
|
|
1759
|
+
currentTrashCount: this.#getTrashBucket(ownerAddress).length,
|
|
1760
|
+
currentCidCount: currentCids.size,
|
|
1761
|
+
importFileCount: normalized.files.length,
|
|
1762
|
+
importTrashCount: normalized.trashFiles.length,
|
|
1763
|
+
requiredBytes,
|
|
1764
|
+
availableBytes,
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
async importUserMetadata(input = {}, options = {}) {
|
|
1769
|
+
this.#ensureInitialized()
|
|
1770
|
+
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1771
|
+
if (!ownerAddress) {
|
|
1772
|
+
throw new ValidationError('valid owner address is required')
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
const checkId = String(options.checkId || '').trim()
|
|
1776
|
+
const check = this.#importChecks.get(checkId)
|
|
1777
|
+
if (
|
|
1778
|
+
!check ||
|
|
1779
|
+
check.ownerAddress !== ownerAddress ||
|
|
1780
|
+
check.expiresAt < Date.now() ||
|
|
1781
|
+
check.packageHash !== this.#hashImportPackage(input)
|
|
1782
|
+
) {
|
|
1783
|
+
throw new ValidationError('valid import check is required')
|
|
1784
|
+
}
|
|
1785
|
+
this.#importChecks.delete(checkId)
|
|
1786
|
+
|
|
1787
|
+
const normalized = this.#normalizeUserImportPackage(input)
|
|
1788
|
+
const previousCids = this.#collectUserCids(ownerAddress)
|
|
1789
|
+
const previous = {
|
|
1790
|
+
removedFiles: this.#getPublishedBucket(ownerAddress).length,
|
|
1791
|
+
removedTrashFiles: this.#getTrashBucket(ownerAddress).length,
|
|
1792
|
+
}
|
|
1793
|
+
const now = new Date().toISOString()
|
|
1794
|
+
|
|
1795
|
+
this.#removeUserFromChannels(ownerAddress)
|
|
1796
|
+
this.#setPublishedBucket(
|
|
1797
|
+
ownerAddress,
|
|
1798
|
+
normalized.files.map(file => ({
|
|
1799
|
+
fileName: file.fileName,
|
|
1800
|
+
cid: file.cid,
|
|
1801
|
+
driveName: file.driveName || this.#getCidInfo(file.cid).driveName,
|
|
1802
|
+
publishedAt: file.publishedAt || now,
|
|
1803
|
+
starred: file.starred || false,
|
|
1804
|
+
}))
|
|
1805
|
+
)
|
|
1806
|
+
this.#setTrashBucket(
|
|
1807
|
+
ownerAddress,
|
|
1808
|
+
normalized.trashFiles.map(file => ({
|
|
1809
|
+
fileName: file.fileName,
|
|
1810
|
+
cid: file.cid,
|
|
1811
|
+
driveName: file.driveName || this.#getCidInfo(file.cid).driveName,
|
|
1812
|
+
size: Number(file.size) || 0,
|
|
1813
|
+
source: file.source || 'imported',
|
|
1814
|
+
publishedAt: file.publishedAt || now,
|
|
1815
|
+
starred: file.starred || false,
|
|
1816
|
+
deletedAt: file.deletedAt || now,
|
|
1817
|
+
}))
|
|
1818
|
+
)
|
|
1819
|
+
this.#applyImportedChannels(ownerAddress, normalized.channels)
|
|
1628
1820
|
this.#savePublishedMetadata()
|
|
1629
1821
|
this.#saveTrashMetadata()
|
|
1630
1822
|
this.#saveChannelsMetadata()
|
|
1823
|
+
previous.removedReplicas = await this.#cleanupUnreferencedCids(previousCids)
|
|
1631
1824
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
const driveName = this.#getCidInfo(cid).driveName
|
|
1825
|
+
const importedFiles = []
|
|
1826
|
+
const failedFiles = []
|
|
1827
|
+
for (const file of normalized.files) {
|
|
1636
1828
|
try {
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1829
|
+
const result = await this.pullByCid({
|
|
1830
|
+
link: buildMostLink(file.cid, file.fileName),
|
|
1831
|
+
ownerAddress,
|
|
1832
|
+
timeout: options.timeout,
|
|
1833
|
+
})
|
|
1834
|
+
importedFiles.push({ cid: file.cid, fileName: file.fileName, result })
|
|
1835
|
+
} catch (err) {
|
|
1836
|
+
failedFiles.push({
|
|
1837
|
+
cid: file.cid,
|
|
1838
|
+
fileName: file.fileName,
|
|
1839
|
+
error: err.message,
|
|
1840
|
+
code: err.code || 'IMPORT_PULL_FAILED',
|
|
1841
|
+
})
|
|
1842
|
+
}
|
|
1644
1843
|
}
|
|
1645
1844
|
|
|
1646
1845
|
return {
|
|
1846
|
+
success: failedFiles.length === 0,
|
|
1647
1847
|
ownerAddress,
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1848
|
+
replacedFiles: previous.removedFiles,
|
|
1849
|
+
replacedTrashFiles: previous.removedTrashFiles,
|
|
1850
|
+
importedFiles: importedFiles.length,
|
|
1851
|
+
importedTrashFiles: normalized.trashFiles.length,
|
|
1852
|
+
failedFiles,
|
|
1651
1853
|
}
|
|
1652
1854
|
}
|
|
1653
1855
|
|
|
@@ -1906,11 +2108,14 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1906
2108
|
|
|
1907
2109
|
async #getLocalCidContent(cid, options = {}) {
|
|
1908
2110
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1909
|
-
const
|
|
1910
|
-
f =>
|
|
1911
|
-
f.cid === cid &&
|
|
1912
|
-
(options.public || this.#recordMatchesOwner(f, ownerAddress))
|
|
2111
|
+
const ownerRecord = this.#getPublishedBucket(ownerAddress).find(
|
|
2112
|
+
f => f.cid === cid
|
|
1913
2113
|
)
|
|
2114
|
+
const fileRecord =
|
|
2115
|
+
ownerRecord ||
|
|
2116
|
+
(options.public
|
|
2117
|
+
? this.#allPublishedRecords().find(f => f.cid === cid)
|
|
2118
|
+
: null)
|
|
1914
2119
|
if (!options.allowHoldingFallback && !fileRecord) {
|
|
1915
2120
|
return null
|
|
1916
2121
|
}
|
|
@@ -1945,7 +2150,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1945
2150
|
cid,
|
|
1946
2151
|
fileName: holding?.fileName || cid,
|
|
1947
2152
|
driveName: holding?.driveName || driveName,
|
|
1948
|
-
localPath: holding?.localPath || null,
|
|
1949
2153
|
size,
|
|
1950
2154
|
ownerAddress,
|
|
1951
2155
|
},
|
|
@@ -2249,9 +2453,36 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2249
2453
|
return trimmed
|
|
2250
2454
|
}
|
|
2251
2455
|
|
|
2456
|
+
setChannelPinned(name, pinned, options = {}) {
|
|
2457
|
+
this.#ensureInitialized()
|
|
2458
|
+
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
2459
|
+
if (!ownerAddress) {
|
|
2460
|
+
throw new Error('需要登录才能设置置顶')
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
const channel = this.#channels.find(c => c.name === name)
|
|
2464
|
+
if (!channel) {
|
|
2465
|
+
throw new Error('频道不存在')
|
|
2466
|
+
}
|
|
2467
|
+
this.#assertChannelMember(name, ownerAddress)
|
|
2468
|
+
|
|
2469
|
+
if (!channel.pinnedBy) {
|
|
2470
|
+
channel.pinnedBy = {}
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
if (pinned) {
|
|
2474
|
+
channel.pinnedBy[ownerAddress] = true
|
|
2475
|
+
} else {
|
|
2476
|
+
delete channel.pinnedBy[ownerAddress]
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
this.#saveChannelsMetadata()
|
|
2480
|
+
return Boolean(channel.pinnedBy[ownerAddress])
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2252
2483
|
/**
|
|
2253
2484
|
* 列出所有频道
|
|
2254
|
-
* @returns {Array<{ name: string, coreKey: string, createdAt: string, type: string, peerCount: number, remark: string }>}
|
|
2485
|
+
* @returns {Array<{ name: string, coreKey: string, createdAt: string, lastMessageAt: string, type: string, peerCount: number, remark: string, pinned: boolean }>}
|
|
2255
2486
|
*/
|
|
2256
2487
|
listChannels(options = {}) {
|
|
2257
2488
|
this.#ensureInitialized()
|
|
@@ -2273,9 +2504,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2273
2504
|
name: c.name,
|
|
2274
2505
|
coreKey: c.coreKey,
|
|
2275
2506
|
createdAt: c.createdAt,
|
|
2507
|
+
lastMessageAt: c.lastMessageAt || '',
|
|
2276
2508
|
type: c.type,
|
|
2277
2509
|
peerCount: (this.#channelPeers.get(c.name) || new Map()).size,
|
|
2278
2510
|
remark: ownerAddress && c.remarks ? c.remarks[ownerAddress] || '' : '',
|
|
2511
|
+
pinned: Boolean(ownerAddress && c.pinnedBy?.[ownerAddress]),
|
|
2279
2512
|
}))
|
|
2280
2513
|
}
|
|
2281
2514
|
|
|
@@ -2405,6 +2638,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2405
2638
|
}
|
|
2406
2639
|
|
|
2407
2640
|
await core.append(message)
|
|
2641
|
+
if (channel) {
|
|
2642
|
+
channel.lastMessageAt = new Date(message.timestamp).toISOString()
|
|
2643
|
+
this.#saveChannelsMetadata()
|
|
2644
|
+
}
|
|
2408
2645
|
|
|
2409
2646
|
return message
|
|
2410
2647
|
}
|
|
@@ -2626,6 +2863,114 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2626
2863
|
}
|
|
2627
2864
|
}
|
|
2628
2865
|
|
|
2866
|
+
#hashImportPackage(input) {
|
|
2867
|
+
return crypto
|
|
2868
|
+
.createHash('sha256')
|
|
2869
|
+
.update(JSON.stringify(input || {}))
|
|
2870
|
+
.digest('hex')
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
#normalizeUserImportPackage(input = {}) {
|
|
2874
|
+
if (!input || typeof input !== 'object') {
|
|
2875
|
+
throw new ValidationError('import package must be an object')
|
|
2876
|
+
}
|
|
2877
|
+
if (Number(input.schemaVersion) !== USER_DATA_SCHEMA_VERSION) {
|
|
2878
|
+
throw new ValidationError('unsupported import package schemaVersion')
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
const normalizeFile = (record, scope) => {
|
|
2882
|
+
if (!record || typeof record !== 'object') {
|
|
2883
|
+
throw new ValidationError(`${scope} record must be an object`)
|
|
2884
|
+
}
|
|
2885
|
+
const cid = String(record.cid || '').trim()
|
|
2886
|
+
const { driveName } = this.#getCidInfo(cid)
|
|
2887
|
+
const fileName = sanitizeFilename(record.fileName || cid)
|
|
2888
|
+
if (!fileName || fileName === 'unnamed') {
|
|
2889
|
+
throw new ValidationError(`${scope} fileName is invalid`)
|
|
2890
|
+
}
|
|
2891
|
+
return {
|
|
2892
|
+
cid,
|
|
2893
|
+
fileName,
|
|
2894
|
+
driveName,
|
|
2895
|
+
size: Number(record.size) || 0,
|
|
2896
|
+
source: String(record.source || 'imported'),
|
|
2897
|
+
publishedAt:
|
|
2898
|
+
typeof record.publishedAt === 'string' ? record.publishedAt : '',
|
|
2899
|
+
deletedAt: typeof record.deletedAt === 'string' ? record.deletedAt : '',
|
|
2900
|
+
starred: Boolean(record.starred),
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
const files = Array.isArray(input.files)
|
|
2905
|
+
? input.files.map(record => normalizeFile(record, 'files'))
|
|
2906
|
+
: []
|
|
2907
|
+
const trashFiles = Array.isArray(input.trashFiles)
|
|
2908
|
+
? input.trashFiles.map(record => normalizeFile(record, 'trashFiles'))
|
|
2909
|
+
: []
|
|
2910
|
+
const channels = Array.isArray(input.channels)
|
|
2911
|
+
? input.channels
|
|
2912
|
+
.filter(channel => channel && typeof channel === 'object')
|
|
2913
|
+
.map(channel => ({
|
|
2914
|
+
name: String(channel.name || '').trim(),
|
|
2915
|
+
type: String(channel.type || 'personal').trim() || 'personal',
|
|
2916
|
+
coreKey: String(channel.coreKey || '').trim(),
|
|
2917
|
+
createdAt:
|
|
2918
|
+
typeof channel.createdAt === 'string' ? channel.createdAt : '',
|
|
2919
|
+
lastMessageAt:
|
|
2920
|
+
typeof channel.lastMessageAt === 'string'
|
|
2921
|
+
? channel.lastMessageAt
|
|
2922
|
+
: '',
|
|
2923
|
+
member:
|
|
2924
|
+
channel.member && typeof channel.member === 'object'
|
|
2925
|
+
? channel.member
|
|
2926
|
+
: null,
|
|
2927
|
+
remark: String(channel.remark || '').slice(0, 50),
|
|
2928
|
+
pinned: Boolean(channel.pinned),
|
|
2929
|
+
}))
|
|
2930
|
+
.filter(channel => CHANNEL_NAME_REGEX.test(channel.name))
|
|
2931
|
+
: []
|
|
2932
|
+
|
|
2933
|
+
return { files, trashFiles, channels }
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
#applyImportedChannels(ownerAddress, channels = []) {
|
|
2937
|
+
const normalizedOwner = normalizeOwnerAddress(ownerAddress)
|
|
2938
|
+
if (!normalizedOwner) return
|
|
2939
|
+
|
|
2940
|
+
for (const imported of channels) {
|
|
2941
|
+
let channel = this.#channels.find(item => item.name === imported.name)
|
|
2942
|
+
if (!channel) {
|
|
2943
|
+
const discoveryKey = this.#generateChannelDiscoveryKey(imported.name)
|
|
2944
|
+
channel = {
|
|
2945
|
+
name: imported.name,
|
|
2946
|
+
discoveryKey: b4a.toString(discoveryKey, 'hex'),
|
|
2947
|
+
coreKey: imported.coreKey,
|
|
2948
|
+
createdAt: imported.createdAt || new Date().toISOString(),
|
|
2949
|
+
lastMessageAt: imported.lastMessageAt || '',
|
|
2950
|
+
type: imported.type,
|
|
2951
|
+
members: [],
|
|
2952
|
+
remoteCoreKeys: [],
|
|
2953
|
+
}
|
|
2954
|
+
this.#channels.push(channel)
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
this.#upsertChannelMember(channel, {
|
|
2958
|
+
ownerAddress: normalizedOwner,
|
|
2959
|
+
displayName: imported.member?.displayName || imported.member?.name || '',
|
|
2960
|
+
avatar: imported.member?.avatar || '',
|
|
2961
|
+
})
|
|
2962
|
+
|
|
2963
|
+
if (imported.remark) {
|
|
2964
|
+
channel.remarks = channel.remarks || {}
|
|
2965
|
+
channel.remarks[normalizedOwner] = imported.remark
|
|
2966
|
+
}
|
|
2967
|
+
if (imported.pinned) {
|
|
2968
|
+
channel.pinnedBy = channel.pinnedBy || {}
|
|
2969
|
+
channel.pinnedBy[normalizedOwner] = true
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2629
2974
|
#getFileRuntimeStats(cid) {
|
|
2630
2975
|
const state = this.#fileMonitors.get(cid)
|
|
2631
2976
|
if (!state) {
|
|
@@ -2793,7 +3138,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2793
3138
|
cid,
|
|
2794
3139
|
fileName: record.fileName || cid,
|
|
2795
3140
|
size,
|
|
2796
|
-
localPath: record.localPath || null,
|
|
2797
3141
|
topic: topicHex,
|
|
2798
3142
|
driveName,
|
|
2799
3143
|
source: record.source || 'manual',
|
|
@@ -2977,26 +3321,163 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2977
3321
|
return drive
|
|
2978
3322
|
}
|
|
2979
3323
|
|
|
2980
|
-
#
|
|
3324
|
+
#getOwnerKey(ownerAddress) {
|
|
3325
|
+
return getOwnerBucketKey(ownerAddress)
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
#getPublishedBucket(ownerAddress, create = false) {
|
|
3329
|
+
const ownerKey = this.#getOwnerKey(ownerAddress)
|
|
3330
|
+
if (!this.#publishedFiles[ownerKey] && create) {
|
|
3331
|
+
this.#publishedFiles[ownerKey] = []
|
|
3332
|
+
}
|
|
3333
|
+
return this.#publishedFiles[ownerKey] || []
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
#getTrashBucket(ownerAddress, create = false) {
|
|
3337
|
+
const ownerKey = this.#getOwnerKey(ownerAddress)
|
|
3338
|
+
if (!this.#trashFiles[ownerKey] && create) {
|
|
3339
|
+
this.#trashFiles[ownerKey] = []
|
|
3340
|
+
}
|
|
3341
|
+
return this.#trashFiles[ownerKey] || []
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
#setPublishedBucket(ownerAddress, records) {
|
|
3345
|
+
const ownerKey = this.#getOwnerKey(ownerAddress)
|
|
3346
|
+
const next = Array.isArray(records) ? records : []
|
|
3347
|
+
if (next.length === 0) {
|
|
3348
|
+
delete this.#publishedFiles[ownerKey]
|
|
3349
|
+
} else {
|
|
3350
|
+
this.#publishedFiles[ownerKey] = next
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
#setTrashBucket(ownerAddress, records) {
|
|
3355
|
+
const ownerKey = this.#getOwnerKey(ownerAddress)
|
|
3356
|
+
const next = Array.isArray(records) ? records : []
|
|
3357
|
+
if (next.length === 0) {
|
|
3358
|
+
delete this.#trashFiles[ownerKey]
|
|
3359
|
+
} else {
|
|
3360
|
+
this.#trashFiles[ownerKey] = next
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
#allPublishedRecords() {
|
|
3365
|
+
return Object.entries(this.#publishedFiles).flatMap(([owner, records]) =>
|
|
3366
|
+
records.map(record => cloneMetadataRecord(record, owner))
|
|
3367
|
+
)
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
#allTrashRecords() {
|
|
3371
|
+
return Object.entries(this.#trashFiles).flatMap(([owner, records]) =>
|
|
3372
|
+
records.map(record => cloneMetadataRecord(record, owner))
|
|
3373
|
+
)
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
#countBucketRecords(buckets) {
|
|
3377
|
+
return Object.values(buckets || {}).reduce(
|
|
3378
|
+
(sum, records) => sum + (Array.isArray(records) ? records.length : 0),
|
|
3379
|
+
0
|
|
3380
|
+
)
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
#collectUserCids(ownerAddress) {
|
|
3384
|
+
const cids = new Set()
|
|
3385
|
+
for (const file of this.#getPublishedBucket(ownerAddress)) {
|
|
3386
|
+
cids.add(file.cid)
|
|
3387
|
+
}
|
|
3388
|
+
for (const file of this.#getTrashBucket(ownerAddress)) {
|
|
3389
|
+
cids.add(file.cid)
|
|
3390
|
+
}
|
|
3391
|
+
return cids
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
#removeUserFromChannels(ownerAddress) {
|
|
2981
3395
|
const normalizedOwner = normalizeOwnerAddress(ownerAddress)
|
|
2982
|
-
if (!normalizedOwner) return
|
|
2983
|
-
|
|
3396
|
+
if (!normalizedOwner) return
|
|
3397
|
+
|
|
3398
|
+
this.#channels = this.#channels
|
|
3399
|
+
.map(channel => {
|
|
3400
|
+
const remarks = channel.remarks
|
|
3401
|
+
? Object.fromEntries(
|
|
3402
|
+
Object.entries(channel.remarks).filter(
|
|
3403
|
+
([address]) => normalizeOwnerAddress(address) !== normalizedOwner
|
|
3404
|
+
)
|
|
3405
|
+
)
|
|
3406
|
+
: undefined
|
|
3407
|
+
const pinnedBy = channel.pinnedBy
|
|
3408
|
+
? Object.fromEntries(
|
|
3409
|
+
Object.entries(channel.pinnedBy).filter(
|
|
3410
|
+
([address]) => normalizeOwnerAddress(address) !== normalizedOwner
|
|
3411
|
+
)
|
|
3412
|
+
)
|
|
3413
|
+
: undefined
|
|
3414
|
+
return {
|
|
3415
|
+
...channel,
|
|
3416
|
+
remarks:
|
|
3417
|
+
remarks && Object.keys(remarks).length > 0 ? remarks : undefined,
|
|
3418
|
+
pinnedBy:
|
|
3419
|
+
pinnedBy && Object.keys(pinnedBy).length > 0 ? pinnedBy : undefined,
|
|
3420
|
+
members: Array.isArray(channel.members)
|
|
3421
|
+
? channel.members.filter(
|
|
3422
|
+
member =>
|
|
3423
|
+
normalizeOwnerAddress(member?.address) !== normalizedOwner
|
|
3424
|
+
)
|
|
3425
|
+
: [],
|
|
3426
|
+
}
|
|
3427
|
+
})
|
|
3428
|
+
.filter(channel => channel.members.length > 0)
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
async #cleanupUnreferencedCids(cids) {
|
|
3432
|
+
let removedReplicas = 0
|
|
3433
|
+
for (const cid of cids) {
|
|
3434
|
+
if (this.#hasAnyUserReference(cid)) continue
|
|
3435
|
+
const driveName = this.#getCidInfo(cid).driveName
|
|
3436
|
+
try {
|
|
3437
|
+
const drive = await this.#getOrCreateDrive(driveName)
|
|
3438
|
+
await drive.del('/' + cid)
|
|
3439
|
+
} catch {}
|
|
3440
|
+
await this.#closeDriveForSeed(driveName)
|
|
3441
|
+
await this.#leaveCidTopic(cid)
|
|
3442
|
+
this.#removeHolding(cid)
|
|
3443
|
+
removedReplicas += 1
|
|
3444
|
+
}
|
|
3445
|
+
return removedReplicas
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
async #clearUserDataInternal(ownerAddress) {
|
|
3449
|
+
const affectedCids = this.#collectUserCids(ownerAddress)
|
|
3450
|
+
const removedFiles = this.#getPublishedBucket(ownerAddress).length
|
|
3451
|
+
const removedTrashFiles = this.#getTrashBucket(ownerAddress).length
|
|
3452
|
+
|
|
3453
|
+
this.#setPublishedBucket(ownerAddress, [])
|
|
3454
|
+
this.#setTrashBucket(ownerAddress, [])
|
|
3455
|
+
this.#removeUserFromChannels(ownerAddress)
|
|
3456
|
+
this.#savePublishedMetadata()
|
|
3457
|
+
this.#saveTrashMetadata()
|
|
3458
|
+
this.#saveChannelsMetadata()
|
|
3459
|
+
|
|
3460
|
+
const removedReplicas = await this.#cleanupUnreferencedCids(affectedCids)
|
|
3461
|
+
return {
|
|
3462
|
+
removedFiles,
|
|
3463
|
+
removedTrashFiles,
|
|
3464
|
+
removedReplicas,
|
|
3465
|
+
}
|
|
2984
3466
|
}
|
|
2985
3467
|
|
|
2986
3468
|
#assertDisplayNameAvailable(fileName, options = {}) {
|
|
2987
3469
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
3470
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
2988
3471
|
const safeFileName = sanitizeFilename(fileName)
|
|
2989
3472
|
const folder = getDisplayPathFolder(safeFileName)
|
|
2990
3473
|
const baseName = getPathBaseName(safeFileName)
|
|
2991
|
-
const conflict =
|
|
3474
|
+
const conflict = files.find(file => {
|
|
2992
3475
|
if (
|
|
2993
3476
|
options.excludeCid &&
|
|
2994
|
-
file.cid === options.excludeCid
|
|
2995
|
-
this.#recordMatchesOwner(file, ownerAddress)
|
|
3477
|
+
file.cid === options.excludeCid
|
|
2996
3478
|
) {
|
|
2997
3479
|
return false
|
|
2998
3480
|
}
|
|
2999
|
-
if (!this.#recordMatchesOwner(file, ownerAddress)) return false
|
|
3000
3481
|
const existingFileName = sanitizeFilename(file.fileName)
|
|
3001
3482
|
return (
|
|
3002
3483
|
getDisplayPathFolder(existingFileName) === folder &&
|
|
@@ -3009,13 +3490,13 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3009
3490
|
}
|
|
3010
3491
|
|
|
3011
3492
|
#hasPublishedReference(cid) {
|
|
3012
|
-
return this.#
|
|
3493
|
+
return this.#allPublishedRecords().some(file => file.cid === cid)
|
|
3013
3494
|
}
|
|
3014
3495
|
|
|
3015
3496
|
#hasAnyUserReference(cid) {
|
|
3016
3497
|
return (
|
|
3017
|
-
this.#
|
|
3018
|
-
this.#
|
|
3498
|
+
this.#allPublishedRecords().some(file => file.cid === cid) ||
|
|
3499
|
+
this.#allTrashRecords().some(file => file.cid === cid)
|
|
3019
3500
|
)
|
|
3020
3501
|
}
|
|
3021
3502
|
|
|
@@ -3080,7 +3561,13 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3080
3561
|
if (fs.existsSync(metadataPath)) {
|
|
3081
3562
|
const data = fs.readFileSync(metadataPath, 'utf-8')
|
|
3082
3563
|
const parsed = JSON.parse(data)
|
|
3083
|
-
|
|
3564
|
+
const buckets = normalizeMetadataBuckets(parsed)
|
|
3565
|
+
for (const records of Object.values(buckets)) {
|
|
3566
|
+
for (const record of records) {
|
|
3567
|
+
record.starred = record.starred || false
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
return buckets
|
|
3084
3571
|
}
|
|
3085
3572
|
} catch (err) {
|
|
3086
3573
|
console.warn(
|
|
@@ -3134,7 +3621,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3134
3621
|
const metadataPath = this.#getTrashMetadataPath()
|
|
3135
3622
|
if (fs.existsSync(metadataPath)) {
|
|
3136
3623
|
const data = fs.readFileSync(metadataPath, 'utf-8')
|
|
3137
|
-
return JSON.parse(data)
|
|
3624
|
+
return normalizeMetadataBuckets(JSON.parse(data))
|
|
3138
3625
|
}
|
|
3139
3626
|
} catch (err) {
|
|
3140
3627
|
console.warn(
|
|
@@ -3181,7 +3668,13 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3181
3668
|
#saveChannelsMetadata() {
|
|
3182
3669
|
try {
|
|
3183
3670
|
const metadataPath = this.#getChannelsMetadataPath()
|
|
3184
|
-
this.#
|
|
3671
|
+
const persistentChannels = this.#channels.filter(
|
|
3672
|
+
channel => !TRANSIENT_CHANNEL_TYPES.has(channel?.type)
|
|
3673
|
+
)
|
|
3674
|
+
this.#atomicWrite(
|
|
3675
|
+
metadataPath,
|
|
3676
|
+
JSON.stringify(persistentChannels, null, 2)
|
|
3677
|
+
)
|
|
3185
3678
|
} catch (err) {
|
|
3186
3679
|
console.error('Failed to save channels metadata:', err.message)
|
|
3187
3680
|
}
|
|
@@ -3211,6 +3704,15 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3211
3704
|
try {
|
|
3212
3705
|
const entry = await core.get(i)
|
|
3213
3706
|
if (entry && entry.type === 'message') {
|
|
3707
|
+
const channel = this.#channels.find(c => c.name === channelName)
|
|
3708
|
+
if (channel) {
|
|
3709
|
+
const entryTime = Number(entry.timestamp) || Date.now()
|
|
3710
|
+
const currentTime = Date.parse(channel.lastMessageAt || '') || 0
|
|
3711
|
+
if (entryTime > currentTime) {
|
|
3712
|
+
channel.lastMessageAt = new Date(entryTime).toISOString()
|
|
3713
|
+
this.#saveChannelsMetadata()
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3214
3716
|
this.emit('channel:message', {
|
|
3215
3717
|
channel: channelName,
|
|
3216
3718
|
message: this.#normalizeChannelMessageForResponse(
|