Skip to content

Commit

Permalink
feat(gql): utilize drive ID in conflict resolution and list folder qu…
Browse files Browse the repository at this point in the history
…eries PE-6232
  • Loading branch information
fedellen committed Jun 3, 2024
1 parent bd6f0d1 commit 35c0a88
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 36 deletions.
75 changes: 54 additions & 21 deletions src/ardrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,11 @@ export class ArDrive extends ArDriveAnonymous {
}

// Assert that there are no duplicate names in the destination folder
const entityNamesInParentFolder = await this.arFsDao.getPublicEntityNamesInFolder(newParentFolderId, owner);
const entityNamesInParentFolder = await this.arFsDao.getPublicEntityNamesInFolder(
newParentFolderId,
owner,
destFolderDriveId
);
if (entityNamesInParentFolder.includes(originalFileMetaData.name)) {
// TODO: Add optional interactive prompt to resolve name conflicts in ticket PE-599
throw new Error(errorMessage.entityNameExists);
Expand Down Expand Up @@ -269,7 +273,8 @@ export class ArDrive extends ArDriveAnonymous {
const entityNamesInParentFolder = await this.arFsDao.getPrivateEntityNamesInFolder(
newParentFolderId,
owner,
driveKey
driveKey,
destFolderDriveId
);
if (entityNamesInParentFolder.includes(originalFileMetaData.name)) {
// TODO: Add optional interactive prompt to resolve name conflicts in ticket PE-599
Expand Down Expand Up @@ -353,7 +358,11 @@ export class ArDrive extends ArDriveAnonymous {
}

// Assert that there are no duplicate names in the destination folder
const entityNamesInParentFolder = await this.arFsDao.getPublicEntityNamesInFolder(newParentFolderId, owner);
const entityNamesInParentFolder = await this.arFsDao.getPublicEntityNamesInFolder(
newParentFolderId,
owner,
destFolderDriveId
);
if (entityNamesInParentFolder.includes(originalFolderMetaData.name)) {
// TODO: Add optional interactive prompt to resolve name conflicts in ticket PE-599
throw new Error(errorMessage.entityNameExists);
Expand Down Expand Up @@ -439,7 +448,8 @@ export class ArDrive extends ArDriveAnonymous {
const entityNamesInParentFolder = await this.arFsDao.getPrivateEntityNamesInFolder(
newParentFolderId,
owner,
driveKey
driveKey,
destFolderDriveId
);
if (entityNamesInParentFolder.includes(originalFolderMetaData.name)) {
// TODO: Add optional interactive prompt to resolve name conflicts in ticket PE-599
Expand Down Expand Up @@ -523,14 +533,14 @@ export class ArDrive extends ArDriveAnonymous {
const resolvedEntitiesToUpload: UploadStats[] = [];

for (const entity of entitiesToUpload) {
const { destFolderId, wrappedEntity, driveKey, owner, destName } = entity;
const { destFolderId, wrappedEntity, driveKey, owner, destName, destDriveId } = entity;

const resolveConflictParams = {
conflictResolution,
getConflictInfoFn: (folderId: FolderID) =>
driveKey
? this.arFsDao.getPrivateNameConflictInfoInFolder(folderId, owner, driveKey)
: this.arFsDao.getPublicNameConflictInfoInFolder(folderId, owner),
? this.arFsDao.getPrivateNameConflictInfoInFolder(folderId, owner, driveKey, destDriveId)
: this.arFsDao.getPublicNameConflictInfoInFolder(folderId, owner, destDriveId),
prompts,
destFolderId
};
Expand Down Expand Up @@ -729,6 +739,7 @@ export class ArDrive extends ArDriveAnonymous {
destinationFolderId
}: RetryPublicArFSFileByDestFolderIdParams): Promise<ArFSResult> {
const metaDataTx = await this.deriveMetaDataTxFromPublicFolder(destinationFolderId, dataTxId);
const driveId = await this.arFsDao.getDriveIdForFolderId(destinationFolderId);

let metaDataTxId: undefined | TransactionID = undefined;
let createMetaDataPlan: undefined | ArFSCreateFileMetaDataV2Plan = undefined;
Expand All @@ -743,7 +754,8 @@ export class ArDrive extends ArDriveAnonymous {
const isValidUpload = await this.assertWriteFileMetaData({
wrappedFile,
conflictResolution,
destinationFolderId
destinationFolderId,
driveId
});

if (!isValidUpload) {
Expand Down Expand Up @@ -827,11 +839,13 @@ export class ArDrive extends ArDriveAnonymous {
dataTxId: TransactionID
): Promise<ArFSPublicFile | undefined> {
const owner = await this.wallet.getAddress();
const driveId = await this.arFsDao.getDriveIdForFolderId(destinationFolderId);
await this.assertFolderExists(destinationFolderId, owner);

const allFileMetaDataTxInFolder = await this.arFsDao.getPublicFilesWithParentFolderIds(
[destinationFolderId],
owner
owner,
driveId
);
const metaDataTxsForThisTx = allFileMetaDataTxInFolder.filter((f) => `${f.dataTxId}` === `${dataTxId}`);

Expand All @@ -853,19 +867,22 @@ export class ArDrive extends ArDriveAnonymous {
private async assertWriteFileMetaData({
wrappedFile,
destinationFolderId,
conflictResolution
conflictResolution,
driveId
}: {
wrappedFile: ArFSFileToUpload;
destinationFolderId: FolderID;
conflictResolution: FileNameConflictResolution;
driveId: DriveID;
}): Promise<boolean> {
const owner = await this.wallet.getAddress();
await resolveFileNameConflicts({
wrappedFile,
conflictResolution,
destFolderId: destinationFolderId,
destinationFileName: wrappedFile.destinationBaseName,
getConflictInfoFn: (folderId: FolderID) => this.arFsDao.getPublicNameConflictInfoInFolder(folderId, owner)
getConflictInfoFn: (folderId: FolderID) =>
this.arFsDao.getPublicNameConflictInfoInFolder(folderId, owner, driveId)
});

if (wrappedFile.conflictResolution) {
Expand Down Expand Up @@ -1019,7 +1036,11 @@ export class ArDrive extends ArDriveAnonymous {
const owner = await this.wallet.getAddress();

// Assert that there are no duplicate names in the destination folder
const entityNamesInParentFolder = await this.arFsDao.getPublicEntityNamesInFolder(parentFolderId, owner);
const entityNamesInParentFolder = await this.arFsDao.getPublicEntityNamesInFolder(
parentFolderId,
owner,
driveId
);
if (entityNamesInParentFolder.includes(folderName)) {
// TODO: Add optional interactive prompt to resolve name conflicts in ticket PE-599
throw new Error(errorMessage.entityNameExists);
Expand Down Expand Up @@ -1083,7 +1104,8 @@ export class ArDrive extends ArDriveAnonymous {
const entityNamesInParentFolder = await this.arFsDao.getPrivateEntityNamesInFolder(
parentFolderId,
owner,
driveKey
driveKey,
driveId
);
if (entityNamesInParentFolder.includes(folderName)) {
// TODO: Add optional interactive prompt to resolve name conflicts in ticket PE-599
Expand Down Expand Up @@ -1438,10 +1460,11 @@ export class ArDrive extends ArDriveAnonymous {
await fileToDownload.write();
}

async assertUniqueNameWithinPublicFolder(name: string, folderId: FolderID): Promise<void> {
async assertUniqueNameWithinPublicFolder(name: string, folderId: FolderID, driveId: DriveID): Promise<void> {
const allSiblingNames = await this.arFsDao.getPublicEntityNamesInFolder(
folderId,
await this.wallet.getAddress()
await this.wallet.getAddress(),
driveId
);
const collidesWithExistingSiblingName = allSiblingNames.reduce((accumulator, siblingName) => {
return accumulator || siblingName === name;
Expand All @@ -1451,11 +1474,17 @@ export class ArDrive extends ArDriveAnonymous {
}
}

async assertUniqueNameWithinPrivateFolder(name: string, folderId: FolderID, driveKey: DriveKey): Promise<void> {
async assertUniqueNameWithinPrivateFolder(
name: string,
folderId: FolderID,
driveKey: DriveKey,
driveId: DriveID
): Promise<void> {
const allSiblingNames = await this.arFsDao.getPrivateEntityNamesInFolder(
folderId,
await this.wallet.getAddress(),
driveKey
driveKey,
driveId
);
const collidesWithExistingSiblingName = allSiblingNames.reduce((accumulator, siblingName) => {
return accumulator || siblingName === name;
Expand All @@ -1467,13 +1496,14 @@ export class ArDrive extends ArDriveAnonymous {

async renamePublicFile({ fileId, newName }: RenamePublicFileParams): Promise<ArFSResult> {
const owner = await this.wallet.getAddress();
const driveId = await this.getDriveIdForFileId(fileId);

const file = await this.getPublicFile({ fileId, owner });
if (file.name === newName) {
throw new Error(`To rename a file, the new name must be different`);
}
assertValidArFSFileName(newName);
await this.assertUniqueNameWithinPublicFolder(newName, file.parentFolderId);
await this.assertUniqueNameWithinPublicFolder(newName, file.parentFolderId, driveId);
const fileMetadataTxDataStub = new ArFSPublicFileMetadataTransactionData(
newName,
file.size,
Expand Down Expand Up @@ -1527,11 +1557,12 @@ export class ArDrive extends ArDriveAnonymous {
async renamePrivateFile({ fileId, newName, driveKey }: RenamePrivateFileParams): Promise<ArFSResult> {
const owner = await this.wallet.getAddress();
const file = await this.getPrivateFile({ fileId, driveKey, owner });
const driveId = await this.getDriveIdForFileId(fileId);
if (file.name === newName) {
throw new Error(`To rename a file, the new name must be different`);
}
assertValidArFSFileName(newName);
await this.assertUniqueNameWithinPrivateFolder(newName, file.parentFolderId, driveKey);
await this.assertUniqueNameWithinPrivateFolder(newName, file.parentFolderId, driveKey, driveId);
const fileMetadataTxDataStub = await ArFSPrivateFileMetadataTransactionData.from(
newName,
file.size,
Expand Down Expand Up @@ -1590,6 +1621,7 @@ export class ArDrive extends ArDriveAnonymous {
async renamePublicFolder({ folderId, newName }: RenamePublicFolderParams): Promise<ArFSResult> {
const owner = await this.wallet.getAddress();
const folder = await this.getPublicFolder({ folderId, owner });
const driveId = await this.getDriveIdForFolderId(folderId);
if (`${folder.parentFolderId}` === ROOT_FOLDER_ID_PLACEHOLDER) {
throw new Error(
`The root folder with ID '${folderId}' cannot be renamed as it shares its name with its parent drive. Consider renaming the drive instead.`
Expand All @@ -1599,7 +1631,7 @@ export class ArDrive extends ArDriveAnonymous {
throw new Error(`New folder name '${newName}' must be different from the current folder name!`);
}
assertValidArFSFolderName(newName);
await this.assertUniqueNameWithinPublicFolder(newName, folder.parentFolderId);
await this.assertUniqueNameWithinPublicFolder(newName, folder.parentFolderId, driveId);
const folderMetadataTxDataStub = new ArFSPublicFolderTransactionData(newName, folder.customMetaDataJson);

const metadataRewardSettings = this.uploadPlanner.isTurboUpload()
Expand Down Expand Up @@ -1647,6 +1679,7 @@ export class ArDrive extends ArDriveAnonymous {
async renamePrivateFolder({ folderId, newName, driveKey }: RenamePrivateFolderParams): Promise<ArFSResult> {
const owner = await this.wallet.getAddress();
const folder = await this.getPrivateFolder({ folderId, driveKey, owner });
const driveId = await this.getDriveIdForFolderId(folderId);
if (`${folder.parentFolderId}` === ROOT_FOLDER_ID_PLACEHOLDER) {
throw new Error(
`The root folder with ID '${folderId}' cannot be renamed as it shares its name with its parent drive. Consider renaming the drive instead.`
Expand All @@ -1656,7 +1689,7 @@ export class ArDrive extends ArDriveAnonymous {
throw new Error(`New folder name '${newName}' must be different from the current folder name!`);
}
assertValidArFSFolderName(newName);
await this.assertUniqueNameWithinPrivateFolder(newName, folder.parentFolderId, driveKey);
await this.assertUniqueNameWithinPrivateFolder(newName, folder.parentFolderId, driveKey, driveId);
const folderMetadataTxDataStub = await ArFSPrivateFolderTransactionData.from(
newName,
driveKey,
Expand Down
4 changes: 2 additions & 2 deletions src/arfs/arfs_entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export class ArFSEntity {
readonly contentType: ContentType; // the mime type of the file uploaded. in the case of drives and folders, it is always a JSON file. Public drive/folders must use "application/json" and private drives use "application/octet-stream" since this data is encrypted.
readonly driveId: DriveID; // the unique drive identifier, created with uuidv4 https://www.npmjs.com/package/uuidv4 eg. 41800747-a852-4dc9-9078-6c20f85c0f3a
readonly entityType: EntityType; // the type of ArFS entity this is. this can only be set to "drive", "folder", "file"
readonly name: string; // user defined entity name, cannot be longer than 64 characters. This is stored in the JSON file that is uploaded along with the drive/folder/file metadata transaction
readonly txId: TransactionID; // the arweave transaction id for this entity. 43 numbers/letters eg. 1xRhN90Mu5mEgyyrmnzKgZP0y3aK8AwSucwlCOAwsaI
readonly name: string; // user defined entity name, cannot be longer than 64 characters. This is stored in the JSON file that is uploaded along with the drive/folder/file metadata transaction cspell:disable
readonly txId: TransactionID; // the arweave transaction id for this entity. 43 numbers/letters eg. 1xRhN90Mu5mEgyyrmnzKgZP0y3aK8AwSucwlCOAwsaI cspell:enable
readonly unixTime: UnixTime; // seconds since unix epoch, taken at the time of upload, 10 numbers eg. 1620068042

readonly boost?: FeeMultiple;
Expand Down
44 changes: 33 additions & 11 deletions src/arfs/arfsdao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {
folderIDs: FolderID[],
driveKey: DriveKey,
owner: ArweaveAddress,
driveId: DriveID,
latestRevisionsOnly = false
): Promise<ArFSPrivateFile[]> {
let cursor = '';
Expand All @@ -1555,6 +1556,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {
while (hasNextPage) {
const gqlQuery = buildQuery({
tags: [
{ name: 'Drive-Id', value: `${driveId}` },
{ name: 'Parent-Folder-Id', value: folderIDs.map((fid) => fid.toString()) },
{ name: 'Entity-Type', value: 'file' }
],
Expand Down Expand Up @@ -1593,6 +1595,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {
) =>
| ArFSFileOrFolderBuilder<'file', ArFSFileOrFolderEntity<'file'>>
| ArFSFileOrFolderBuilder<'folder', ArFSFileOrFolderEntity<'folder'>>,
driveId: DriveID,
latestRevisionsOnly = true,
filterOnOwner = true
): Promise<T[]> {
Expand All @@ -1603,6 +1606,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {
while (hasNextPage) {
const gqlQuery = buildQuery({
tags: [
{ name: 'Drive-Id', value: `${driveId}` },
{ name: 'Parent-Folder-Id', value: `${parentFolderId}` },
{ name: 'Entity-Type', value: ['file', 'folder'] }
],
Expand Down Expand Up @@ -1638,6 +1642,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {
parentFolderId: FolderID,
owner: ArweaveAddress,
driveKey: DriveKey,
driveId: DriveID,
latestRevisionsOnly = true
): Promise<(ArFSPrivateFile | ArFSPrivateFolder)[]> {
return this.getEntitiesInFolder(
Expand All @@ -1647,13 +1652,15 @@ export class ArFSDAO extends ArFSDAOAnonymous {
entityType === 'folder'
? ArFSPrivateFolderBuilder.fromArweaveNode(node, this.gatewayApi, driveKey)
: ArFSPrivateFileBuilder.fromArweaveNode(node, this.gatewayApi, driveKey),
driveId,
latestRevisionsOnly
);
}

async getPublicEntitiesInFolder(
parentFolderId: FolderID,
owner: ArweaveAddress,
driveId: DriveID,
latestRevisionsOnly = true
): Promise<(ArFSPublicFile | ArFSPublicFolder)[]> {
return this.getEntitiesInFolder(
Expand All @@ -1663,6 +1670,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {
entityType === 'folder'
? ArFSPublicFolderBuilder.fromArweaveNode(node, this.gatewayApi)
: ArFSPublicFileBuilder.fromArweaveNode(node, this.gatewayApi),
driveId,
latestRevisionsOnly
);
}
Expand All @@ -1678,18 +1686,23 @@ export class ArFSDAO extends ArFSDAOAnonymous {
async getPrivateEntityNamesInFolder(
folderId: FolderID,
owner: ArweaveAddress,
driveKey: DriveKey
driveKey: DriveKey,
driveId: DriveID
): Promise<string[]> {
const childrenOfFolder = await this.getPrivateEntitiesInFolder(folderId, owner, driveKey, true);
const childrenOfFolder = await this.getPrivateEntitiesInFolder(folderId, owner, driveKey, driveId, true);
return childrenOfFolder.map(entityToNameMap);
}

async getPublicEntityNamesInFolder(folderId: FolderID, owner: ArweaveAddress): Promise<string[]> {
const childrenOfFolder = await this.getPublicEntitiesInFolder(folderId, owner, true);
async getPublicEntityNamesInFolder(folderId: FolderID, owner: ArweaveAddress, driveId: DriveID): Promise<string[]> {
const childrenOfFolder = await this.getPublicEntitiesInFolder(folderId, owner, driveId, true);
return childrenOfFolder.map(entityToNameMap);
}

async getPublicNameConflictInfoInFolder(folderId: FolderID, owner: ArweaveAddress): Promise<NameConflictInfo> {
async getPublicNameConflictInfoInFolder(
folderId: FolderID,
owner: ArweaveAddress,
driveId: DriveID
): Promise<NameConflictInfo> {
const cacheKey = { folderId, owner };
const cachedConflictInfo = this.caches.publicConflictCache.get(cacheKey);
if (cachedConflictInfo) {
Expand All @@ -1699,7 +1712,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {
return this.caches.publicConflictCache.put(
cacheKey,
(async () => {
const childrenOfFolder = await this.getPublicEntitiesInFolder(folderId, owner, true);
const childrenOfFolder = await this.getPublicEntitiesInFolder(folderId, owner, driveId, true);
return {
files: childrenOfFolder.filter(fileFilter).map(fileConflictInfoMap),
folders: childrenOfFolder.filter(folderFilter).map(folderToNameAndIdMap)
Expand All @@ -1711,7 +1724,8 @@ export class ArFSDAO extends ArFSDAOAnonymous {
async getPrivateNameConflictInfoInFolder(
folderId: FolderID,
owner: ArweaveAddress,
driveKey: DriveKey
driveKey: DriveKey,
driveId: DriveID
): Promise<NameConflictInfo> {
const cacheKey = { folderId, owner, driveKey };
const cachedConflictInfo = this.caches.privateConflictCache.get(cacheKey);
Expand All @@ -1722,7 +1736,13 @@ export class ArFSDAO extends ArFSDAOAnonymous {
return this.caches.privateConflictCache.put(
cacheKey,
(async () => {
const childrenOfFolder = await this.getPrivateEntitiesInFolder(folderId, owner, driveKey, true);
const childrenOfFolder = await this.getPrivateEntitiesInFolder(
folderId,
owner,
driveKey,
driveId,
true
);
// Hack to deal with potential typescript bug
const files = childrenOfFolder.filter(fileFilter) as ArFSPrivateFile[];
const folders = childrenOfFolder.filter(folderFilter) as ArFSPrivateFolder[];
Expand Down Expand Up @@ -2282,9 +2302,11 @@ export class ArFSDAO extends ArFSDAOAnonymous {
// Fetch all file entities within all Folders of the drive
const childFiles: ArFSPrivateFile[] = [];
for (const id of searchFolderIDs) {
(await this.getPrivateFilesWithParentFolderIds([id], driveKey, owner, true)).forEach((e) => {
childFiles.push(e);
});
(await this.getPrivateFilesWithParentFolderIds([id], driveKey, owner, driveIdOfFolder, true)).forEach(
(e) => {
childFiles.push(e);
}
);
}

const [, ...subFolderIDs]: FolderID[] = hierarchy.folderIdSubtreeFromFolderId(folder.entityId, maxDepth + 1);
Expand Down
Loading

0 comments on commit 35c0a88

Please sign in to comment.