@fluidframework/map 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.0
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/CHANGELOG.md +4 -0
- package/dist/directory.js +121 -97
- package/dist/directory.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/directory.js +121 -97
- package/lib/directory.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +12 -12
- package/src/directory.ts +158 -117
- package/src/packageVersion.ts +1 -1
package/CHANGELOG.md
CHANGED
package/dist/directory.js
CHANGED
|
@@ -661,18 +661,17 @@ function isClearLocalOpMetadata(metadata) {
|
|
|
661
661
|
}
|
|
662
662
|
function isSubDirLocalOpMetadata(metadata) {
|
|
663
663
|
return (metadata !== undefined &&
|
|
664
|
-
typeof metadata.pendingMessageId === "number" &&
|
|
665
664
|
(metadata.type === "createSubDir" || metadata.type === "deleteSubDir"));
|
|
666
665
|
}
|
|
667
666
|
function isDirectoryLocalOpMetadata(metadata) {
|
|
668
|
-
return (metadata
|
|
669
|
-
|
|
670
|
-
(metadata
|
|
671
|
-
metadata.type === "deleteSubDir" ||
|
|
672
|
-
(metadata.type === "clear" && typeof metadata.previousStorage === "object") ||
|
|
673
|
-
metadata.type === "createSubDir"));
|
|
667
|
+
return (isKeyEditLocalOpMetadata(metadata) ||
|
|
668
|
+
isClearLocalOpMetadata(metadata) ||
|
|
669
|
+
isSubDirLocalOpMetadata(metadata));
|
|
674
670
|
}
|
|
675
671
|
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
|
|
672
|
+
function assertNonNullClientId(clientId) {
|
|
673
|
+
(0, common_utils_1.assert)(clientId !== null, 0x6af /* client id should never be null */);
|
|
674
|
+
}
|
|
676
675
|
/**
|
|
677
676
|
* Node of the directory tree.
|
|
678
677
|
* @sealed
|
|
@@ -712,18 +711,24 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
712
711
|
*/
|
|
713
712
|
this._subdirectories = new Map();
|
|
714
713
|
/**
|
|
715
|
-
* Keys that have been modified locally but not yet ack'd from the server.
|
|
714
|
+
* Keys that have been modified locally but not yet ack'd from the server. This is for operations on keys like
|
|
715
|
+
* set/delete operations on keys. The value of this map is list of pendingMessageIds at which that key
|
|
716
|
+
* was modified. We don't store the type of ops, and behaviour of key ops are different from behaviour of sub
|
|
717
|
+
* directory ops, so we have separate map from subDirectories tracker.
|
|
716
718
|
*/
|
|
717
719
|
this.pendingKeys = new Map();
|
|
718
720
|
/**
|
|
719
|
-
* Subdirectories that have been
|
|
721
|
+
* Subdirectories that have been deleted locally but not yet ack'd from the server. This maintains the record
|
|
722
|
+
* of delete op that are pending or yet to be acked from server. This is maintained just to track the locally
|
|
723
|
+
* deleted sub directory.
|
|
720
724
|
*/
|
|
721
|
-
this.
|
|
725
|
+
this.pendingDeleteSubDirectoriesTracker = new Map();
|
|
722
726
|
/**
|
|
723
|
-
* Subdirectories that have been
|
|
724
|
-
* of
|
|
727
|
+
* Subdirectories that have been created locally but not yet ack'd from the server. This maintains the record
|
|
728
|
+
* of create op that are pending or yet to be acked from server. This is maintained just to track the locally
|
|
729
|
+
* created sub directory.
|
|
725
730
|
*/
|
|
726
|
-
this.
|
|
731
|
+
this.pendingCreateSubDirectoriesTracker = new Map();
|
|
727
732
|
/**
|
|
728
733
|
* This is used to assign a unique id to every outgoing operation and helps in tracking unack'd ops.
|
|
729
734
|
*/
|
|
@@ -890,8 +895,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
890
895
|
* @returns - true if there is pending delete.
|
|
891
896
|
*/
|
|
892
897
|
isSubDirectoryDeletePending(subDirName) {
|
|
893
|
-
|
|
894
|
-
if (pendingDeleteSubDirectory !== undefined && pendingDeleteSubDirectory > 0) {
|
|
898
|
+
if (this.pendingDeleteSubDirectoriesTracker.has(subDirName)) {
|
|
895
899
|
return true;
|
|
896
900
|
}
|
|
897
901
|
return false;
|
|
@@ -1131,9 +1135,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1131
1135
|
*/
|
|
1132
1136
|
processCreateSubDirectoryMessage(msg, op, local, localOpMetadata) {
|
|
1133
1137
|
this.throwIfDisposed();
|
|
1134
|
-
if (!this.
|
|
1138
|
+
if (!(this.isMessageForCurrentInstanceOfSubDirectory(msg) &&
|
|
1139
|
+
this.needProcessSubDirectoryOperation(msg, op, local, localOpMetadata))) {
|
|
1135
1140
|
return;
|
|
1136
1141
|
}
|
|
1142
|
+
assertNonNullClientId(msg.clientId);
|
|
1137
1143
|
this.createSubDirectoryCore(op.subdirName, local, msg.sequenceNumber, msg.clientId);
|
|
1138
1144
|
}
|
|
1139
1145
|
/**
|
|
@@ -1146,10 +1152,9 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1146
1152
|
this.throwIfDisposed();
|
|
1147
1153
|
// Create the sub directory locally first.
|
|
1148
1154
|
this.createSubDirectoryCore(op.subdirName, true, -1, (_c = this.runtime.clientId) !== null && _c !== void 0 ? _c : "detached");
|
|
1149
|
-
|
|
1155
|
+
this.updatePendingSubDirMessageCount(op);
|
|
1150
1156
|
const localOpMetadata = {
|
|
1151
1157
|
type: "createSubDir",
|
|
1152
|
-
pendingMessageId: newMessageId,
|
|
1153
1158
|
};
|
|
1154
1159
|
return localOpMetadata;
|
|
1155
1160
|
}
|
|
@@ -1178,10 +1183,9 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1178
1183
|
applyStashedDeleteSubDirMessage(op) {
|
|
1179
1184
|
this.throwIfDisposed();
|
|
1180
1185
|
const subDir = this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1181
|
-
|
|
1186
|
+
this.updatePendingSubDirMessageCount(op);
|
|
1182
1187
|
const metadata = {
|
|
1183
1188
|
type: "deleteSubDir",
|
|
1184
|
-
pendingMessageId: newMessageId,
|
|
1185
1189
|
subDirectory: subDir,
|
|
1186
1190
|
};
|
|
1187
1191
|
return metadata;
|
|
@@ -1210,8 +1214,11 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1210
1214
|
(0, common_utils_1.assert)(isClearLocalOpMetadata(localOpMetadata), 0x32b /* Invalid localOpMetadata for clear */);
|
|
1211
1215
|
// We don't reuse the metadata pendingMessageId but send a new one on each submit.
|
|
1212
1216
|
const pendingClearMessageId = this.pendingClearMessageIds.shift();
|
|
1213
|
-
|
|
1214
|
-
|
|
1217
|
+
// Only submit the op, if we have record for it, otherwise it is possible that the older instance
|
|
1218
|
+
// is already deleted, in which case we don't need to submit the op.
|
|
1219
|
+
if (pendingClearMessageId === localOpMetadata.pendingMessageId) {
|
|
1220
|
+
this.submitClearMessage(op, localOpMetadata.previousStorage);
|
|
1221
|
+
}
|
|
1215
1222
|
}
|
|
1216
1223
|
/**
|
|
1217
1224
|
* Get a new pending message id for the op and cache it to track the pending op
|
|
@@ -1249,33 +1256,41 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1249
1256
|
(0, common_utils_1.assert)(isKeyEditLocalOpMetadata(localOpMetadata), 0x32d /* Invalid localOpMetadata in submit */);
|
|
1250
1257
|
// clear the old pending message id
|
|
1251
1258
|
const pendingMessageIds = this.pendingKeys.get(op.key);
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
pendingMessageIds
|
|
1255
|
-
|
|
1256
|
-
|
|
1259
|
+
// Only submit the op, if we have record for it, otherwise it is possible that the older instance
|
|
1260
|
+
// is already deleted, in which case we don't need to submit the op.
|
|
1261
|
+
if (pendingMessageIds !== undefined &&
|
|
1262
|
+
pendingMessageIds[0] === localOpMetadata.pendingMessageId) {
|
|
1263
|
+
pendingMessageIds.shift();
|
|
1264
|
+
if (pendingMessageIds.length === 0) {
|
|
1265
|
+
this.pendingKeys.delete(op.key);
|
|
1266
|
+
}
|
|
1267
|
+
this.submitKeyMessage(op, localOpMetadata.previousValue);
|
|
1257
1268
|
}
|
|
1258
|
-
this.submitKeyMessage(op, localOpMetadata.previousValue);
|
|
1259
1269
|
}
|
|
1260
|
-
|
|
1261
|
-
* Get a new pending message id for the op and cache it to track the pending op
|
|
1262
|
-
*/
|
|
1263
|
-
getSubDirMessageId(op) {
|
|
1270
|
+
incrementPendingSubDirCount(map, subDirName) {
|
|
1264
1271
|
var _c;
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1272
|
+
const count = (_c = map.get(subDirName)) !== null && _c !== void 0 ? _c : 0;
|
|
1273
|
+
map.set(subDirName, count + 1);
|
|
1274
|
+
}
|
|
1275
|
+
decrementPendingSubDirCount(map, subDirName) {
|
|
1276
|
+
var _c;
|
|
1277
|
+
const count = (_c = map.get(subDirName)) !== null && _c !== void 0 ? _c : 0;
|
|
1278
|
+
map.set(subDirName, count - 1);
|
|
1279
|
+
if (count <= 1) {
|
|
1280
|
+
map.delete(subDirName);
|
|
1273
1281
|
}
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Update the count for pending create/delete of the sub directory so that it can be validated on receiving op
|
|
1285
|
+
* or while resubmitting the op.
|
|
1286
|
+
*/
|
|
1287
|
+
updatePendingSubDirMessageCount(op) {
|
|
1274
1288
|
if (op.type === "deleteSubDirectory") {
|
|
1275
|
-
|
|
1276
|
-
|
|
1289
|
+
this.incrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subdirName);
|
|
1290
|
+
}
|
|
1291
|
+
else if (op.type === "createSubDirectory") {
|
|
1292
|
+
this.incrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
|
|
1277
1293
|
}
|
|
1278
|
-
return newMessageId;
|
|
1279
1294
|
}
|
|
1280
1295
|
/**
|
|
1281
1296
|
* Submit a create subdirectory operation.
|
|
@@ -1283,10 +1298,9 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1283
1298
|
*/
|
|
1284
1299
|
submitCreateSubDirectoryMessage(op) {
|
|
1285
1300
|
this.throwIfDisposed();
|
|
1286
|
-
|
|
1301
|
+
this.updatePendingSubDirMessageCount(op);
|
|
1287
1302
|
const localOpMetadata = {
|
|
1288
1303
|
type: "createSubDir",
|
|
1289
|
-
pendingMessageId: newMessageId,
|
|
1290
1304
|
};
|
|
1291
1305
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
1292
1306
|
}
|
|
@@ -1297,10 +1311,9 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1297
1311
|
*/
|
|
1298
1312
|
submitDeleteSubDirectoryMessage(op, subDir) {
|
|
1299
1313
|
this.throwIfDisposed();
|
|
1300
|
-
|
|
1314
|
+
this.updatePendingSubDirMessageCount(op);
|
|
1301
1315
|
const localOpMetadata = {
|
|
1302
1316
|
type: "deleteSubDir",
|
|
1303
|
-
pendingMessageId: newMessageId,
|
|
1304
1317
|
subDirectory: subDir,
|
|
1305
1318
|
};
|
|
1306
1319
|
this.directory.submitDirectoryMessage(op, localOpMetadata);
|
|
@@ -1313,18 +1326,22 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1313
1326
|
*/
|
|
1314
1327
|
resubmitSubDirectoryMessage(op, localOpMetadata) {
|
|
1315
1328
|
(0, common_utils_1.assert)(isSubDirLocalOpMetadata(localOpMetadata), 0x32f /* Invalid localOpMetadata for sub directory op */);
|
|
1316
|
-
//
|
|
1317
|
-
|
|
1318
|
-
(
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1329
|
+
// Only submit the op, if we have record for it, otherwise it is possible that the older instance
|
|
1330
|
+
// is already deleted, in which case we don't need to submit the op.
|
|
1331
|
+
if (localOpMetadata.type === "createSubDir" &&
|
|
1332
|
+
!this.pendingCreateSubDirectoriesTracker.has(op.subdirName)) {
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
else if (localOpMetadata.type === "deleteSubDir" &&
|
|
1336
|
+
!this.pendingDeleteSubDirectoriesTracker.has(op.subdirName)) {
|
|
1337
|
+
return;
|
|
1323
1338
|
}
|
|
1324
1339
|
if (localOpMetadata.type === "createSubDir") {
|
|
1340
|
+
this.decrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
|
|
1325
1341
|
this.submitCreateSubDirectoryMessage(op);
|
|
1326
1342
|
}
|
|
1327
1343
|
else {
|
|
1344
|
+
this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subdirName);
|
|
1328
1345
|
this.submitDeleteSubDirectoryMessage(op, localOpMetadata.subDirectory);
|
|
1329
1346
|
}
|
|
1330
1347
|
}
|
|
@@ -1428,7 +1445,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1428
1445
|
}
|
|
1429
1446
|
else if (op.type === "createSubDirectory" && localOpMetadata.type === "createSubDir") {
|
|
1430
1447
|
this.deleteSubDirectoryCore(op.subdirName, true);
|
|
1431
|
-
this.
|
|
1448
|
+
this.decrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
|
|
1432
1449
|
}
|
|
1433
1450
|
else if (op.type === "deleteSubDirectory" && localOpMetadata.type === "deleteSubDir") {
|
|
1434
1451
|
if (localOpMetadata.subDirectory !== undefined) {
|
|
@@ -1437,13 +1454,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1437
1454
|
this._subdirectories.set(op.subdirName, localOpMetadata.subDirectory);
|
|
1438
1455
|
this.emit("subDirectoryCreated", op.subdirName, true, this);
|
|
1439
1456
|
}
|
|
1440
|
-
this.
|
|
1441
|
-
const count = this.pendingDeleteSubDirectoriesCount.get(op.subdirName);
|
|
1442
|
-
(0, common_utils_1.assert)(count !== undefined && count > 0, 0x5ab /* should have record for delete op */);
|
|
1443
|
-
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1444
|
-
if (count === 1) {
|
|
1445
|
-
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1446
|
-
}
|
|
1457
|
+
this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subDirName);
|
|
1447
1458
|
}
|
|
1448
1459
|
else {
|
|
1449
1460
|
throw new Error("Unsupported op for rollback");
|
|
@@ -1521,7 +1532,7 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1521
1532
|
// If the message is either from the creator of directory or this directory was created when
|
|
1522
1533
|
// container was detached or in case this directory is already live(known to other clients)
|
|
1523
1534
|
// and the op was created after the directory was created then apply this op.
|
|
1524
|
-
return (this.clientIds.has(msg.clientId) ||
|
|
1535
|
+
return ((msg.clientId !== null && this.clientIds.has(msg.clientId)) ||
|
|
1525
1536
|
this.clientIds.has("detached") ||
|
|
1526
1537
|
(this.sequenceNumber !== -1 && this.sequenceNumber <= msg.referenceSequenceNumber));
|
|
1527
1538
|
}
|
|
@@ -1536,50 +1547,63 @@ class SubDirectory extends common_utils_1.TypedEventEmitter {
|
|
|
1536
1547
|
* @returns True if the operation should be processed, false otherwise
|
|
1537
1548
|
*/
|
|
1538
1549
|
needProcessSubDirectoryOperation(msg, op, local, localOpMetadata) {
|
|
1539
|
-
|
|
1540
|
-
|
|
1550
|
+
assertNonNullClientId(msg.clientId);
|
|
1551
|
+
const pendingDeleteCount = this.pendingDeleteSubDirectoriesTracker.get(op.subdirName);
|
|
1552
|
+
const pendingCreateCount = this.pendingCreateSubDirectoriesTracker.get(op.subdirName);
|
|
1553
|
+
if ((pendingDeleteCount !== undefined && pendingDeleteCount > 0) ||
|
|
1554
|
+
(pendingCreateCount !== undefined && pendingCreateCount > 0)) {
|
|
1541
1555
|
if (local) {
|
|
1542
1556
|
(0, common_utils_1.assert)(isSubDirLocalOpMetadata(localOpMetadata), 0x012 /* pendingMessageId is missing from the local client's operation */);
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
pendingMessageIds.shift();
|
|
1547
|
-
if (pendingMessageIds.length === 0) {
|
|
1548
|
-
this.pendingSubDirectories.delete(op.subdirName);
|
|
1557
|
+
if (localOpMetadata.type === "deleteSubDir") {
|
|
1558
|
+
(0, common_utils_1.assert)(pendingDeleteCount !== undefined && pendingDeleteCount > 0, 0x6c2 /* pendingDeleteCount should exist */);
|
|
1559
|
+
this.decrementPendingSubDirCount(this.pendingDeleteSubDirectoriesTracker, op.subdirName);
|
|
1549
1560
|
}
|
|
1550
|
-
if (
|
|
1551
|
-
|
|
1552
|
-
(
|
|
1553
|
-
this.pendingDeleteSubDirectoriesCount.set(op.subdirName, count - 1);
|
|
1554
|
-
if (count === 1) {
|
|
1555
|
-
this.pendingDeleteSubDirectoriesCount.delete(op.subdirName);
|
|
1556
|
-
}
|
|
1561
|
+
else if (localOpMetadata.type === "createSubDir") {
|
|
1562
|
+
(0, common_utils_1.assert)(pendingCreateCount !== undefined && pendingCreateCount > 0, 0x6c3 /* pendingCreateCount should exist */);
|
|
1563
|
+
this.decrementPendingSubDirCount(this.pendingCreateSubDirectoriesTracker, op.subdirName);
|
|
1557
1564
|
}
|
|
1558
1565
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
subDirectory
|
|
1565
|
-
//
|
|
1566
|
+
if (op.type === "deleteSubDirectory") {
|
|
1567
|
+
const resetSubDirectoryTree = (directory) => {
|
|
1568
|
+
if (!directory) {
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
// If this is delete op and we have keys in this subDirectory, then we need to delete these
|
|
1572
|
+
// keys except the pending ones as they will be sequenced after this delete.
|
|
1573
|
+
directory.clearExceptPendingKeys(local);
|
|
1574
|
+
// In case of delete op, we need to reset the creation seq number and client ids of
|
|
1566
1575
|
// creators as the previous directory is getting deleted and we will initialize again when
|
|
1567
1576
|
// we will receive op for the create again.
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1577
|
+
directory.sequenceNumber = -1;
|
|
1578
|
+
directory.clientIds.clear();
|
|
1579
|
+
// Do the same thing for the subtree of the directory. If create is not pending for a child, then just
|
|
1580
|
+
// delete it.
|
|
1581
|
+
const subDirectories = directory.subdirectories();
|
|
1582
|
+
for (const [subDirName, subDir] of subDirectories) {
|
|
1583
|
+
if (directory.pendingCreateSubDirectoriesTracker.has(subDirName)) {
|
|
1584
|
+
resetSubDirectoryTree(subDir);
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
directory.deleteSubDirectoryCore(subDirName, false);
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
const subDirectory = this._subdirectories.get(op.subdirName);
|
|
1591
|
+
resetSubDirectoryTree(subDirectory);
|
|
1571
1592
|
}
|
|
1572
1593
|
if (op.type === "createSubDirectory") {
|
|
1573
1594
|
const dir = this._subdirectories.get(op.subdirName);
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
dir.sequenceNumber
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
dir
|
|
1582
|
-
|
|
1595
|
+
// Child sub directory create seq number can't be lower than the parent subdirectory.
|
|
1596
|
+
if (this.sequenceNumber !== -1 && this.sequenceNumber < msg.sequenceNumber) {
|
|
1597
|
+
if ((dir === null || dir === void 0 ? void 0 : dir.sequenceNumber) === -1) {
|
|
1598
|
+
// Only set the seq on the first message, could be more
|
|
1599
|
+
dir.sequenceNumber = msg.sequenceNumber;
|
|
1600
|
+
}
|
|
1601
|
+
// The client created the dir at or after the dirs seq, so list its client id as a creator.
|
|
1602
|
+
if (dir !== undefined &&
|
|
1603
|
+
!dir.clientIds.has(msg.clientId) &&
|
|
1604
|
+
dir.sequenceNumber <= msg.sequenceNumber) {
|
|
1605
|
+
dir.clientIds.add(msg.clientId);
|
|
1606
|
+
}
|
|
1583
1607
|
}
|
|
1584
1608
|
}
|
|
1585
1609
|
return false;
|