most-box 0.1.7 → 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/out/404/index.html +2 -2
- package/out/404.html +2 -2
- package/out/__next.__PAGE__.txt +4 -4
- package/out/__next._full.txt +13 -13
- package/out/__next._head.txt +3 -3
- package/out/__next._index.txt +7 -7
- package/out/__next._tree.txt +2 -2
- 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/1x76jmdus29em.js +1 -0
- package/out/_next/static/chunks/22vt64m0krmev.js +1 -0
- package/out/_next/static/chunks/266xuv6ts5889.js +5 -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/3ej6im7cbakx3.js +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/{43l72x-2z3zxb.js → 3uxltn4677hjy.js} +1 -1
- package/out/_next/static/chunks/3w2qk10_udrd9.js +1 -0
- package/out/_next/static/chunks/3xl0fq2d3dle6.js +1 -0
- package/out/_next/static/chunks/{418_591yp64lx.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 +15 -14
- package/out/demo/__next._head.txt +3 -3
- package/out/demo/__next._index.txt +7 -7
- package/out/demo/__next._tree.txt +4 -3
- package/out/demo/__next.demo.__PAGE__.txt +6 -5
- package/out/demo/__next.demo.txt +3 -3
- package/out/demo/index.html +3 -3
- package/out/demo/index.txt +15 -14
- package/out/download/__next._full.txt +36 -34
- package/out/download/__next._head.txt +3 -3
- package/out/download/__next._index.txt +7 -7
- package/out/download/__next._tree.txt +2 -2
- package/out/download/__next.download.__PAGE__.txt +5 -5
- package/out/download/__next.download.txt +3 -3
- package/out/download/index.html +2 -2
- package/out/download/index.txt +36 -34
- 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 +13 -13
- 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 +2 -2
- package/out/game/gandengyan/__next.game.gandengyan.__PAGE__.txt +4 -4
- 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 +13 -13
- package/out/game/index.html +1 -1
- package/out/game/index.txt +11 -11
- package/out/game/zhajinhua/__next._full.txt +13 -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 +2 -2
- package/out/game/zhajinhua/__next.game.txt +3 -3
- package/out/game/zhajinhua/__next.game.zhajinhua.__PAGE__.txt +4 -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 +13 -13
- package/out/index.html +2 -2
- package/out/index.txt +13 -13
- 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 +13 -13
- package/out/ping/__next._head.txt +3 -3
- package/out/ping/__next._index.txt +7 -7
- package/out/ping/__next._tree.txt +2 -2
- package/out/ping/__next.ping.__PAGE__.txt +4 -4
- package/out/ping/__next.ping.txt +3 -3
- package/out/ping/index.html +2 -2
- package/out/ping/index.txt +13 -13
- 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/http/app.js +66 -1
- package/server/src/http/nodeStatus.js +18 -0
- package/server/src/index.js +659 -164
- package/out/_next/static/chunks/0-c8h4u1i2f78.js +0 -1
- package/out/_next/static/chunks/02j6ojja7vzp8.js +0 -1
- package/out/_next/static/chunks/06im3as4nwfil.js +0 -1
- package/out/_next/static/chunks/0sq_0pwxnhby2.js +0 -1
- package/out/_next/static/chunks/1ei8b9nw-ea8p.css +0 -1
- package/out/_next/static/chunks/22h69_zwmttt4.js +0 -1
- package/out/_next/static/chunks/23zlmarf-go2x.js +0 -1
- package/out/_next/static/chunks/24k_4-y4ym0df.js +0 -1
- package/out/_next/static/chunks/2693fczty5yr8.js +0 -1
- package/out/_next/static/chunks/2_i1b78h0yiu_.js +0 -1
- package/out/_next/static/chunks/2fmkcw8ui5xp3.js +0 -5
- package/out/_next/static/chunks/2nbju8t9-ux58.css +0 -1
- package/out/_next/static/chunks/3fea100s764m-.js +0 -1
- package/out/_next/static/chunks/3mccxoa60ley8.js +0 -1
- /package/out/_next/static/{eXddCOt9eyz0j78b1m4Ex → TXy93SnAZQ7OQ-aVUkmHa}/_buildManifest.js +0 -0
- /package/out/_next/static/{eXddCOt9eyz0j78b1m4Ex → TXy93SnAZQ7OQ-aVUkmHa}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{eXddCOt9eyz0j78b1m4Ex → TXy93SnAZQ7OQ-aVUkmHa}/_ssgManifest.js +0 -0
package/server/src/index.js
CHANGED
|
@@ -68,12 +68,44 @@ import {
|
|
|
68
68
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
69
69
|
const CHAT_FILE_ROOT = 'chat-file'
|
|
70
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
|
|
71
75
|
|
|
72
76
|
function normalizeOwnerAddress(address) {
|
|
73
77
|
const value = String(address || '').trim()
|
|
74
78
|
return /^0x[a-fA-F0-9]{40}$/.test(value) ? value.toLowerCase() : ''
|
|
75
79
|
}
|
|
76
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
|
+
|
|
77
109
|
function getPathBaseName(fileName) {
|
|
78
110
|
const parts = String(fileName || '').split('/').filter(Boolean)
|
|
79
111
|
return parts[parts.length - 1] || 'unnamed_file'
|
|
@@ -123,12 +155,13 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
123
155
|
#store = null
|
|
124
156
|
#swarm = null
|
|
125
157
|
#drives = new Map()
|
|
126
|
-
#publishedFiles =
|
|
158
|
+
#publishedFiles = {}
|
|
127
159
|
#holdings = []
|
|
128
|
-
#trashFiles =
|
|
160
|
+
#trashFiles = {}
|
|
129
161
|
#initialized = false
|
|
130
162
|
#options = null
|
|
131
163
|
#activeDownloads = new Map()
|
|
164
|
+
#importChecks = new Map()
|
|
132
165
|
#drivePromises = new Map()
|
|
133
166
|
#fileDiscoveries = new Map()
|
|
134
167
|
#fileMonitors = new Map()
|
|
@@ -303,7 +336,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
303
336
|
|
|
304
337
|
this.#publishedFiles = this.#loadPublishedMetadata()
|
|
305
338
|
console.log(
|
|
306
|
-
`[MostBox] Loaded ${this.#publishedFiles
|
|
339
|
+
`[MostBox] Loaded ${this.#countBucketRecords(this.#publishedFiles)} published files`
|
|
307
340
|
)
|
|
308
341
|
|
|
309
342
|
this.#holdings = this.#loadHoldingsMetadata()
|
|
@@ -318,7 +351,9 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
318
351
|
}
|
|
319
352
|
|
|
320
353
|
this.#trashFiles = this.#loadTrashMetadata()
|
|
321
|
-
console.log(
|
|
354
|
+
console.log(
|
|
355
|
+
`[MostBox] Loaded ${this.#countBucketRecords(this.#trashFiles)} trash files`
|
|
356
|
+
)
|
|
322
357
|
|
|
323
358
|
this.#channels = this.#loadChannelsMetadata()
|
|
324
359
|
console.log(`[MostBox] Loaded ${this.#channels.length} channels`)
|
|
@@ -429,6 +464,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
429
464
|
this.#channelChatDiscoveries.clear()
|
|
430
465
|
this.#channelPeers.clear()
|
|
431
466
|
this.#channels = []
|
|
467
|
+
this.#importChecks.clear()
|
|
432
468
|
|
|
433
469
|
if (this.#store) {
|
|
434
470
|
await this.#store.close()
|
|
@@ -471,7 +507,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
471
507
|
* @param {string|Buffer} content - 文件路径(字符串)或内容(Buffer)
|
|
472
508
|
* @param {string} [fileName] - 文件名(Buffer 输入时必填)
|
|
473
509
|
* @param {object} [options] - 发布选项
|
|
474
|
-
* @param {string|null} [options.localPath] - 持有记录中的本地路径
|
|
475
510
|
* @returns {Promise<{ cid: string, link: string, fileName: string }>}
|
|
476
511
|
*/
|
|
477
512
|
async publishFile(content, fileName, options = {}) {
|
|
@@ -526,15 +561,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
526
561
|
const { cid: rootCid } = await calculateCid(content)
|
|
527
562
|
const cidString = rootCid.toString()
|
|
528
563
|
const { driveName: name } = this.#getCidInfo(cidString)
|
|
529
|
-
const
|
|
530
|
-
options.localPath === undefined ? cleanPath : options.localPath
|
|
531
|
-
|
|
564
|
+
const publishedBucket = this.#getPublishedBucket(ownerAddress, true)
|
|
532
565
|
// 检查相同内容是否已存在
|
|
533
|
-
const existingIndex =
|
|
534
|
-
f => f.cid === cidString && this.#recordMatchesOwner(f, ownerAddress)
|
|
535
|
-
)
|
|
566
|
+
const existingIndex = publishedBucket.findIndex(f => f.cid === cidString)
|
|
536
567
|
if (existingIndex !== -1) {
|
|
537
|
-
const existing =
|
|
568
|
+
const existing = publishedBucket[existingIndex]
|
|
538
569
|
await this.#joinCidTopicInternal(cidString, {
|
|
539
570
|
server: true,
|
|
540
571
|
client: false,
|
|
@@ -543,7 +574,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
543
574
|
cid: cidString,
|
|
544
575
|
fileName: existing.fileName,
|
|
545
576
|
size: fileSize,
|
|
546
|
-
localPath: holdingLocalPath,
|
|
547
577
|
driveName: name,
|
|
548
578
|
source: 'published',
|
|
549
579
|
})
|
|
@@ -614,20 +644,18 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
614
644
|
}
|
|
615
645
|
|
|
616
646
|
// 存储 displayName(用户看到的文件夹路径),不存储 drivePath
|
|
617
|
-
|
|
647
|
+
publishedBucket.push({
|
|
618
648
|
fileName: safeFileName,
|
|
619
649
|
cid: cidString,
|
|
620
650
|
driveName: name,
|
|
621
651
|
publishedAt: new Date().toISOString(),
|
|
622
652
|
starred: false,
|
|
623
|
-
ownerAddress,
|
|
624
653
|
})
|
|
625
654
|
this.#savePublishedMetadata()
|
|
626
655
|
this.#upsertHolding({
|
|
627
656
|
cid: cidString,
|
|
628
657
|
fileName: safeFileName,
|
|
629
658
|
size: fileSize,
|
|
630
|
-
localPath: holdingLocalPath,
|
|
631
659
|
driveName: name,
|
|
632
660
|
source: 'published',
|
|
633
661
|
})
|
|
@@ -701,8 +729,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
701
729
|
size:
|
|
702
730
|
existingHolding?.size ??
|
|
703
731
|
(Number.isFinite(localContent.size) ? localContent.size : 0),
|
|
704
|
-
localPath:
|
|
705
|
-
existingHolding?.localPath || existingFile?.localPath || null,
|
|
706
732
|
driveName: existingFile?.driveName || name,
|
|
707
733
|
source: existingHolding?.source || 'published',
|
|
708
734
|
})
|
|
@@ -960,27 +986,26 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
960
986
|
savedPath: savePath,
|
|
961
987
|
}
|
|
962
988
|
|
|
963
|
-
|
|
964
|
-
const existingIndex =
|
|
965
|
-
f => f.cid === cidString
|
|
989
|
+
const publishedBucket = this.#getPublishedBucket(ownerAddress, true)
|
|
990
|
+
const existingIndex = publishedBucket.findIndex(
|
|
991
|
+
f => f.cid === cidString
|
|
966
992
|
)
|
|
967
993
|
this.#assertDisplayNameAvailable(sanitizedFileName, {
|
|
968
994
|
ownerAddress,
|
|
969
995
|
excludeCid: cidString,
|
|
970
996
|
})
|
|
971
997
|
if (existingIndex !== -1) {
|
|
972
|
-
const existing =
|
|
998
|
+
const existing = publishedBucket[existingIndex]
|
|
973
999
|
existing.fileName = sanitizedFileName
|
|
974
1000
|
existing.driveName = name
|
|
975
1001
|
existing.publishedAt = new Date().toISOString()
|
|
976
1002
|
} else {
|
|
977
|
-
|
|
1003
|
+
publishedBucket.push({
|
|
978
1004
|
fileName: sanitizedFileName,
|
|
979
1005
|
cid: cidString,
|
|
980
1006
|
driveName: name,
|
|
981
1007
|
publishedAt: new Date().toISOString(),
|
|
982
1008
|
starred: false,
|
|
983
|
-
ownerAddress,
|
|
984
1009
|
})
|
|
985
1010
|
}
|
|
986
1011
|
this.#savePublishedMetadata()
|
|
@@ -989,7 +1014,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
989
1014
|
cid: cidString,
|
|
990
1015
|
fileName: sanitizedFileName,
|
|
991
1016
|
size: savedSize,
|
|
992
|
-
localPath: savePath,
|
|
993
1017
|
driveName: name,
|
|
994
1018
|
source: 'downloaded',
|
|
995
1019
|
})
|
|
@@ -1119,12 +1143,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1119
1143
|
*/
|
|
1120
1144
|
listPublishedFiles(options = {}) {
|
|
1121
1145
|
this.#ensureInitialized()
|
|
1122
|
-
let files = this.#publishedFiles
|
|
1123
1146
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1124
|
-
|
|
1125
|
-
if (ownerAddress) {
|
|
1126
|
-
files = files.filter(f => this.#recordMatchesOwner(f, ownerAddress))
|
|
1127
|
-
}
|
|
1147
|
+
let files = this.#getPublishedBucket(ownerAddress)
|
|
1128
1148
|
|
|
1129
1149
|
if (options.starred === true) {
|
|
1130
1150
|
files = files.filter(f => f.starred === true)
|
|
@@ -1136,7 +1156,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1136
1156
|
link: `most://${f.cid}?filename=${encodeURIComponent(f.fileName)}`,
|
|
1137
1157
|
publishedAt: f.publishedAt,
|
|
1138
1158
|
starred: f.starred || false,
|
|
1139
|
-
ownerAddress:
|
|
1159
|
+
ownerAddress: ownerAddress || '',
|
|
1140
1160
|
}))
|
|
1141
1161
|
}
|
|
1142
1162
|
|
|
@@ -1148,17 +1168,16 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1148
1168
|
toggleStarred(cid, options = {}) {
|
|
1149
1169
|
this.#ensureInitialized()
|
|
1150
1170
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
)
|
|
1171
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
1172
|
+
const index = files.findIndex(f => f.cid === cid)
|
|
1154
1173
|
if (index === -1) {
|
|
1155
1174
|
throw new Error('File not found')
|
|
1156
1175
|
}
|
|
1157
|
-
|
|
1176
|
+
files[index].starred = !files[index].starred
|
|
1158
1177
|
this.#savePublishedMetadata()
|
|
1159
1178
|
return {
|
|
1160
1179
|
cid,
|
|
1161
|
-
starred:
|
|
1180
|
+
starred: files[index].starred,
|
|
1162
1181
|
}
|
|
1163
1182
|
}
|
|
1164
1183
|
|
|
@@ -1170,29 +1189,28 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1170
1189
|
async deletePublishedFile(cid, options = {}) {
|
|
1171
1190
|
this.#ensureInitialized()
|
|
1172
1191
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
)
|
|
1192
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
1193
|
+
const trashFiles = this.#getTrashBucket(ownerAddress, true)
|
|
1194
|
+
const index = files.findIndex(f => f.cid === cid)
|
|
1176
1195
|
if (index !== -1) {
|
|
1177
|
-
const fileRecord =
|
|
1196
|
+
const fileRecord = files[index]
|
|
1178
1197
|
const holding = this.#holdings.find(item => item.cid === fileRecord.cid)
|
|
1179
1198
|
|
|
1180
|
-
|
|
1199
|
+
trashFiles.push({
|
|
1181
1200
|
fileName: fileRecord.fileName,
|
|
1182
1201
|
cid: fileRecord.cid,
|
|
1183
1202
|
driveName:
|
|
1184
1203
|
fileRecord.driveName || this.#getCidInfo(fileRecord.cid).driveName,
|
|
1185
1204
|
size: holding?.size ?? fileRecord.size ?? 0,
|
|
1186
|
-
localPath: holding?.localPath || fileRecord.localPath || null,
|
|
1187
1205
|
source: holding?.source || 'published',
|
|
1188
1206
|
publishedAt: fileRecord.publishedAt,
|
|
1189
1207
|
starred: fileRecord.starred || false,
|
|
1190
|
-
ownerAddress: fileRecord.ownerAddress || ownerAddress,
|
|
1191
1208
|
deletedAt: new Date().toISOString(),
|
|
1192
1209
|
})
|
|
1193
1210
|
this.#saveTrashMetadata()
|
|
1194
1211
|
|
|
1195
|
-
|
|
1212
|
+
files.splice(index, 1)
|
|
1213
|
+
this.#setPublishedBucket(ownerAddress, files)
|
|
1196
1214
|
this.#savePublishedMetadata()
|
|
1197
1215
|
|
|
1198
1216
|
if (!this.#hasPublishedReference(fileRecord.cid)) {
|
|
@@ -1213,16 +1231,14 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1213
1231
|
listTrashFiles(options = {}) {
|
|
1214
1232
|
this.#ensureInitialized()
|
|
1215
1233
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1216
|
-
const files = ownerAddress
|
|
1217
|
-
? this.#trashFiles.filter(f => this.#recordMatchesOwner(f, ownerAddress))
|
|
1218
|
-
: this.#trashFiles
|
|
1234
|
+
const files = this.#getTrashBucket(ownerAddress)
|
|
1219
1235
|
return files.map(f => ({
|
|
1220
1236
|
fileName: f.fileName,
|
|
1221
1237
|
cid: f.cid,
|
|
1222
1238
|
link: `most://${f.cid}?filename=${encodeURIComponent(f.fileName)}`,
|
|
1223
1239
|
publishedAt: f.publishedAt,
|
|
1224
1240
|
starred: f.starred || false,
|
|
1225
|
-
ownerAddress:
|
|
1241
|
+
ownerAddress: ownerAddress || '',
|
|
1226
1242
|
deletedAt: f.deletedAt,
|
|
1227
1243
|
}))
|
|
1228
1244
|
}
|
|
@@ -1235,22 +1251,23 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1235
1251
|
async restoreTrashFile(cid, options = {}) {
|
|
1236
1252
|
this.#ensureInitialized()
|
|
1237
1253
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
)
|
|
1254
|
+
const trashFiles = this.#getTrashBucket(ownerAddress)
|
|
1255
|
+
const publishedFiles = this.#getPublishedBucket(ownerAddress, true)
|
|
1256
|
+
const index = trashFiles.findIndex(f => f.cid === cid)
|
|
1241
1257
|
if (index === -1) {
|
|
1242
1258
|
throw new Error('File not found in trash')
|
|
1243
1259
|
}
|
|
1244
1260
|
|
|
1245
|
-
const fileRecord =
|
|
1261
|
+
const fileRecord = trashFiles[index]
|
|
1246
1262
|
|
|
1247
1263
|
const { driveName } = this.#getCidInfo(fileRecord.cid)
|
|
1248
1264
|
|
|
1249
|
-
const existingIndex =
|
|
1250
|
-
f => f.cid === fileRecord.cid
|
|
1265
|
+
const existingIndex = publishedFiles.findIndex(
|
|
1266
|
+
f => f.cid === fileRecord.cid
|
|
1251
1267
|
)
|
|
1252
1268
|
if (existingIndex !== -1) {
|
|
1253
|
-
|
|
1269
|
+
trashFiles.splice(index, 1)
|
|
1270
|
+
this.#setTrashBucket(ownerAddress, trashFiles)
|
|
1254
1271
|
this.#saveTrashMetadata()
|
|
1255
1272
|
return this.listPublishedFiles({ ownerAddress })
|
|
1256
1273
|
}
|
|
@@ -1260,17 +1277,17 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1260
1277
|
excludeCid: fileRecord.cid,
|
|
1261
1278
|
})
|
|
1262
1279
|
|
|
1263
|
-
|
|
1280
|
+
publishedFiles.push({
|
|
1264
1281
|
fileName: fileRecord.fileName,
|
|
1265
1282
|
cid: fileRecord.cid,
|
|
1266
1283
|
driveName,
|
|
1267
1284
|
publishedAt: fileRecord.publishedAt,
|
|
1268
1285
|
starred: fileRecord.starred || false,
|
|
1269
|
-
ownerAddress: fileRecord.ownerAddress || ownerAddress,
|
|
1270
1286
|
})
|
|
1271
1287
|
this.#savePublishedMetadata()
|
|
1272
1288
|
|
|
1273
|
-
|
|
1289
|
+
trashFiles.splice(index, 1)
|
|
1290
|
+
this.#setTrashBucket(ownerAddress, trashFiles)
|
|
1274
1291
|
this.#saveTrashMetadata()
|
|
1275
1292
|
|
|
1276
1293
|
await this.#joinCidTopicInternal(fileRecord.cid, {
|
|
@@ -1281,7 +1298,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1281
1298
|
cid: fileRecord.cid,
|
|
1282
1299
|
fileName: fileRecord.fileName,
|
|
1283
1300
|
size: Number(fileRecord.size) || 0,
|
|
1284
|
-
localPath: fileRecord.localPath || null,
|
|
1285
1301
|
driveName,
|
|
1286
1302
|
source: fileRecord.source || 'published',
|
|
1287
1303
|
})
|
|
@@ -1297,15 +1313,15 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1297
1313
|
async permanentDeleteTrashFile(cid, options = {}) {
|
|
1298
1314
|
this.#ensureInitialized()
|
|
1299
1315
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
)
|
|
1316
|
+
const trashFiles = this.#getTrashBucket(ownerAddress)
|
|
1317
|
+
const index = trashFiles.findIndex(f => f.cid === cid)
|
|
1303
1318
|
if (index !== -1) {
|
|
1304
|
-
const fileRecord =
|
|
1319
|
+
const fileRecord = trashFiles[index]
|
|
1305
1320
|
const driveName =
|
|
1306
1321
|
fileRecord.driveName || this.#getCidInfo(fileRecord.cid).driveName
|
|
1307
1322
|
|
|
1308
|
-
|
|
1323
|
+
trashFiles.splice(index, 1)
|
|
1324
|
+
this.#setTrashBucket(ownerAddress, trashFiles)
|
|
1309
1325
|
this.#saveTrashMetadata()
|
|
1310
1326
|
|
|
1311
1327
|
if (!this.#hasAnyUserReference(fileRecord.cid)) {
|
|
@@ -1330,18 +1346,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1330
1346
|
async emptyTrash(options = {}) {
|
|
1331
1347
|
this.#ensureInitialized()
|
|
1332
1348
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1333
|
-
const
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
for (const fileRecord of this.#trashFiles) {
|
|
1337
|
-
if (ownerAddress && !this.#recordMatchesOwner(fileRecord, ownerAddress)) {
|
|
1338
|
-
remainingTrash.push(fileRecord)
|
|
1339
|
-
continue
|
|
1340
|
-
}
|
|
1341
|
-
removedTrash.push(fileRecord)
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
this.#trashFiles = remainingTrash
|
|
1349
|
+
const removedTrash = [...this.#getTrashBucket(ownerAddress)]
|
|
1350
|
+
this.#setTrashBucket(ownerAddress, [])
|
|
1345
1351
|
this.#saveTrashMetadata()
|
|
1346
1352
|
|
|
1347
1353
|
for (const fileRecord of removedTrash) {
|
|
@@ -1420,15 +1426,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1420
1426
|
used: usedSize,
|
|
1421
1427
|
free: freeSize,
|
|
1422
1428
|
fileCount: ownerAddress
|
|
1423
|
-
? this.#
|
|
1424
|
-
|
|
1425
|
-
).length
|
|
1426
|
-
: this.#publishedFiles.length,
|
|
1429
|
+
? this.#getPublishedBucket(ownerAddress).length
|
|
1430
|
+
: this.#countBucketRecords(this.#publishedFiles),
|
|
1427
1431
|
trashCount: ownerAddress
|
|
1428
|
-
? this.#
|
|
1429
|
-
|
|
1430
|
-
).length
|
|
1431
|
-
: this.#trashFiles.length,
|
|
1432
|
+
? this.#getTrashBucket(ownerAddress).length
|
|
1433
|
+
: this.#countBucketRecords(this.#trashFiles),
|
|
1432
1434
|
}
|
|
1433
1435
|
}
|
|
1434
1436
|
|
|
@@ -1442,9 +1444,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1442
1444
|
moveFile(cid, newFileName, options = {}) {
|
|
1443
1445
|
this.#ensureInitialized()
|
|
1444
1446
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1445
|
-
const
|
|
1446
|
-
|
|
1447
|
-
)
|
|
1447
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
1448
|
+
const index = files.findIndex(f => f.cid === cid)
|
|
1448
1449
|
if (index === -1) {
|
|
1449
1450
|
throw new Error('File not found')
|
|
1450
1451
|
}
|
|
@@ -1453,8 +1454,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1453
1454
|
ownerAddress,
|
|
1454
1455
|
excludeCid: cid,
|
|
1455
1456
|
})
|
|
1456
|
-
|
|
1457
|
-
|
|
1457
|
+
files[index].fileName = safeFileName
|
|
1458
|
+
files[index].publishedAt = new Date().toISOString()
|
|
1458
1459
|
this.#savePublishedMetadata()
|
|
1459
1460
|
return {
|
|
1460
1461
|
cid,
|
|
@@ -1475,12 +1476,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1475
1476
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1476
1477
|
const prefix = oldPath + '/'
|
|
1477
1478
|
const updates = []
|
|
1479
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
1478
1480
|
|
|
1479
|
-
for (const file of
|
|
1480
|
-
if (
|
|
1481
|
-
file.fileName.startsWith(prefix) &&
|
|
1482
|
-
this.#recordMatchesOwner(file, ownerAddress)
|
|
1483
|
-
) {
|
|
1481
|
+
for (const file of files) {
|
|
1482
|
+
if (file.fileName.startsWith(prefix)) {
|
|
1484
1483
|
const remainder = file.fileName.substring(prefix.length)
|
|
1485
1484
|
const newFileName = sanitizeFilename(
|
|
1486
1485
|
remainder ? newPath + '/' + remainder : newPath
|
|
@@ -1545,10 +1544,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1545
1544
|
getPublishedFiles(options = {}) {
|
|
1546
1545
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1547
1546
|
return ownerAddress
|
|
1548
|
-
? this.#
|
|
1549
|
-
|
|
1550
|
-
)
|
|
1551
|
-
: this.#publishedFiles
|
|
1547
|
+
? this.#getPublishedBucket(ownerAddress)
|
|
1548
|
+
: this.#allPublishedRecords()
|
|
1552
1549
|
}
|
|
1553
1550
|
|
|
1554
1551
|
listUsers() {
|
|
@@ -1569,17 +1566,21 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1569
1566
|
return users.get(ownerAddress)
|
|
1570
1567
|
}
|
|
1571
1568
|
|
|
1572
|
-
for (const
|
|
1573
|
-
const entry = ensure(
|
|
1569
|
+
for (const [ownerAddress, files] of Object.entries(this.#publishedFiles)) {
|
|
1570
|
+
const entry = ensure(ownerAddress)
|
|
1574
1571
|
if (!entry) continue
|
|
1575
|
-
entry.fileCount +=
|
|
1576
|
-
|
|
1572
|
+
entry.fileCount += files.length
|
|
1573
|
+
for (const file of files) {
|
|
1574
|
+
entry.cids.add(file.cid)
|
|
1575
|
+
}
|
|
1577
1576
|
}
|
|
1578
|
-
for (const
|
|
1579
|
-
const entry = ensure(
|
|
1577
|
+
for (const [ownerAddress, files] of Object.entries(this.#trashFiles)) {
|
|
1578
|
+
const entry = ensure(ownerAddress)
|
|
1580
1579
|
if (!entry) continue
|
|
1581
|
-
entry.trashCount +=
|
|
1582
|
-
|
|
1580
|
+
entry.trashCount += files.length
|
|
1581
|
+
for (const file of files) {
|
|
1582
|
+
entry.cids.add(file.cid)
|
|
1583
|
+
}
|
|
1583
1584
|
}
|
|
1584
1585
|
|
|
1585
1586
|
return [...users.values()].map(user => ({
|
|
@@ -1597,58 +1598,258 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1597
1598
|
throw new ValidationError('valid owner address is required')
|
|
1598
1599
|
}
|
|
1599
1600
|
|
|
1600
|
-
const
|
|
1601
|
-
const beforeFiles = this.#publishedFiles.length
|
|
1602
|
-
const beforeTrash = this.#trashFiles.length
|
|
1601
|
+
const result = await this.#clearUserDataInternal(ownerAddress)
|
|
1603
1602
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
this.#
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
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))
|
|
1619
1637
|
.map(channel => ({
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
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]),
|
|
1626
1648
|
}))
|
|
1627
|
-
.filter(channel => channel.members.length > 0)
|
|
1628
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)
|
|
1629
1820
|
this.#savePublishedMetadata()
|
|
1630
1821
|
this.#saveTrashMetadata()
|
|
1631
1822
|
this.#saveChannelsMetadata()
|
|
1823
|
+
previous.removedReplicas = await this.#cleanupUnreferencedCids(previousCids)
|
|
1632
1824
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
const driveName = this.#getCidInfo(cid).driveName
|
|
1825
|
+
const importedFiles = []
|
|
1826
|
+
const failedFiles = []
|
|
1827
|
+
for (const file of normalized.files) {
|
|
1637
1828
|
try {
|
|
1638
|
-
const
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
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
|
+
}
|
|
1645
1843
|
}
|
|
1646
1844
|
|
|
1647
1845
|
return {
|
|
1846
|
+
success: failedFiles.length === 0,
|
|
1648
1847
|
ownerAddress,
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1848
|
+
replacedFiles: previous.removedFiles,
|
|
1849
|
+
replacedTrashFiles: previous.removedTrashFiles,
|
|
1850
|
+
importedFiles: importedFiles.length,
|
|
1851
|
+
importedTrashFiles: normalized.trashFiles.length,
|
|
1852
|
+
failedFiles,
|
|
1652
1853
|
}
|
|
1653
1854
|
}
|
|
1654
1855
|
|
|
@@ -1907,11 +2108,14 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1907
2108
|
|
|
1908
2109
|
async #getLocalCidContent(cid, options = {}) {
|
|
1909
2110
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1910
|
-
const
|
|
1911
|
-
f =>
|
|
1912
|
-
f.cid === cid &&
|
|
1913
|
-
(options.public || this.#recordMatchesOwner(f, ownerAddress))
|
|
2111
|
+
const ownerRecord = this.#getPublishedBucket(ownerAddress).find(
|
|
2112
|
+
f => f.cid === cid
|
|
1914
2113
|
)
|
|
2114
|
+
const fileRecord =
|
|
2115
|
+
ownerRecord ||
|
|
2116
|
+
(options.public
|
|
2117
|
+
? this.#allPublishedRecords().find(f => f.cid === cid)
|
|
2118
|
+
: null)
|
|
1915
2119
|
if (!options.allowHoldingFallback && !fileRecord) {
|
|
1916
2120
|
return null
|
|
1917
2121
|
}
|
|
@@ -1946,7 +2150,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1946
2150
|
cid,
|
|
1947
2151
|
fileName: holding?.fileName || cid,
|
|
1948
2152
|
driveName: holding?.driveName || driveName,
|
|
1949
|
-
localPath: holding?.localPath || null,
|
|
1950
2153
|
size,
|
|
1951
2154
|
ownerAddress,
|
|
1952
2155
|
},
|
|
@@ -2250,9 +2453,36 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2250
2453
|
return trimmed
|
|
2251
2454
|
}
|
|
2252
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
|
+
|
|
2253
2483
|
/**
|
|
2254
2484
|
* 列出所有频道
|
|
2255
|
-
* @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 }>}
|
|
2256
2486
|
*/
|
|
2257
2487
|
listChannels(options = {}) {
|
|
2258
2488
|
this.#ensureInitialized()
|
|
@@ -2274,9 +2504,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2274
2504
|
name: c.name,
|
|
2275
2505
|
coreKey: c.coreKey,
|
|
2276
2506
|
createdAt: c.createdAt,
|
|
2507
|
+
lastMessageAt: c.lastMessageAt || '',
|
|
2277
2508
|
type: c.type,
|
|
2278
2509
|
peerCount: (this.#channelPeers.get(c.name) || new Map()).size,
|
|
2279
2510
|
remark: ownerAddress && c.remarks ? c.remarks[ownerAddress] || '' : '',
|
|
2511
|
+
pinned: Boolean(ownerAddress && c.pinnedBy?.[ownerAddress]),
|
|
2280
2512
|
}))
|
|
2281
2513
|
}
|
|
2282
2514
|
|
|
@@ -2406,6 +2638,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2406
2638
|
}
|
|
2407
2639
|
|
|
2408
2640
|
await core.append(message)
|
|
2641
|
+
if (channel) {
|
|
2642
|
+
channel.lastMessageAt = new Date(message.timestamp).toISOString()
|
|
2643
|
+
this.#saveChannelsMetadata()
|
|
2644
|
+
}
|
|
2409
2645
|
|
|
2410
2646
|
return message
|
|
2411
2647
|
}
|
|
@@ -2627,6 +2863,114 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2627
2863
|
}
|
|
2628
2864
|
}
|
|
2629
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
|
+
|
|
2630
2974
|
#getFileRuntimeStats(cid) {
|
|
2631
2975
|
const state = this.#fileMonitors.get(cid)
|
|
2632
2976
|
if (!state) {
|
|
@@ -2794,7 +3138,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2794
3138
|
cid,
|
|
2795
3139
|
fileName: record.fileName || cid,
|
|
2796
3140
|
size,
|
|
2797
|
-
localPath: record.localPath || null,
|
|
2798
3141
|
topic: topicHex,
|
|
2799
3142
|
driveName,
|
|
2800
3143
|
source: record.source || 'manual',
|
|
@@ -2978,26 +3321,163 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2978
3321
|
return drive
|
|
2979
3322
|
}
|
|
2980
3323
|
|
|
2981
|
-
#
|
|
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) {
|
|
2982
3395
|
const normalizedOwner = normalizeOwnerAddress(ownerAddress)
|
|
2983
|
-
if (!normalizedOwner) return
|
|
2984
|
-
|
|
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
|
+
}
|
|
2985
3466
|
}
|
|
2986
3467
|
|
|
2987
3468
|
#assertDisplayNameAvailable(fileName, options = {}) {
|
|
2988
3469
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
3470
|
+
const files = this.#getPublishedBucket(ownerAddress)
|
|
2989
3471
|
const safeFileName = sanitizeFilename(fileName)
|
|
2990
3472
|
const folder = getDisplayPathFolder(safeFileName)
|
|
2991
3473
|
const baseName = getPathBaseName(safeFileName)
|
|
2992
|
-
const conflict =
|
|
3474
|
+
const conflict = files.find(file => {
|
|
2993
3475
|
if (
|
|
2994
3476
|
options.excludeCid &&
|
|
2995
|
-
file.cid === options.excludeCid
|
|
2996
|
-
this.#recordMatchesOwner(file, ownerAddress)
|
|
3477
|
+
file.cid === options.excludeCid
|
|
2997
3478
|
) {
|
|
2998
3479
|
return false
|
|
2999
3480
|
}
|
|
3000
|
-
if (!this.#recordMatchesOwner(file, ownerAddress)) return false
|
|
3001
3481
|
const existingFileName = sanitizeFilename(file.fileName)
|
|
3002
3482
|
return (
|
|
3003
3483
|
getDisplayPathFolder(existingFileName) === folder &&
|
|
@@ -3010,13 +3490,13 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3010
3490
|
}
|
|
3011
3491
|
|
|
3012
3492
|
#hasPublishedReference(cid) {
|
|
3013
|
-
return this.#
|
|
3493
|
+
return this.#allPublishedRecords().some(file => file.cid === cid)
|
|
3014
3494
|
}
|
|
3015
3495
|
|
|
3016
3496
|
#hasAnyUserReference(cid) {
|
|
3017
3497
|
return (
|
|
3018
|
-
this.#
|
|
3019
|
-
this.#
|
|
3498
|
+
this.#allPublishedRecords().some(file => file.cid === cid) ||
|
|
3499
|
+
this.#allTrashRecords().some(file => file.cid === cid)
|
|
3020
3500
|
)
|
|
3021
3501
|
}
|
|
3022
3502
|
|
|
@@ -3081,7 +3561,13 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3081
3561
|
if (fs.existsSync(metadataPath)) {
|
|
3082
3562
|
const data = fs.readFileSync(metadataPath, 'utf-8')
|
|
3083
3563
|
const parsed = JSON.parse(data)
|
|
3084
|
-
|
|
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
|
|
3085
3571
|
}
|
|
3086
3572
|
} catch (err) {
|
|
3087
3573
|
console.warn(
|
|
@@ -3135,7 +3621,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3135
3621
|
const metadataPath = this.#getTrashMetadataPath()
|
|
3136
3622
|
if (fs.existsSync(metadataPath)) {
|
|
3137
3623
|
const data = fs.readFileSync(metadataPath, 'utf-8')
|
|
3138
|
-
return JSON.parse(data)
|
|
3624
|
+
return normalizeMetadataBuckets(JSON.parse(data))
|
|
3139
3625
|
}
|
|
3140
3626
|
} catch (err) {
|
|
3141
3627
|
console.warn(
|
|
@@ -3218,6 +3704,15 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3218
3704
|
try {
|
|
3219
3705
|
const entry = await core.get(i)
|
|
3220
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
|
+
}
|
|
3221
3716
|
this.emit('channel:message', {
|
|
3222
3717
|
channel: channelName,
|
|
3223
3718
|
message: this.#normalizeChannelMessageForResponse(
|