diff --git a/src/s3.ts b/src/s3.ts index 1a79867..1de4e01 100644 --- a/src/s3.ts +++ b/src/s3.ts @@ -85,11 +85,12 @@ export const listS3Contents = async ( path?: string ): Promise => { const fileList: IContentsList = {}; + const prefix = path ? PathExt.join(root, path) : root; // listing contents of folder const command = new ListObjectsV2Command({ Bucket: bucketName, - Prefix: path ? PathExt.join(root, path) : root + Prefix: prefix + (prefix ? '/' : '') }); let isTruncated: boolean | undefined = true; @@ -256,21 +257,35 @@ export const createS3Object = async ( if (options) { body = Private.formatBody(options, fileFormat, fileType, fileMimeType); } + let lastModified; - await s3Client.send( - new PutObjectCommand({ - Bucket: bucketName, - Key: path + (PathExt.extname(name) === '' ? '/' : ''), - Body: body, - CacheControl: options ? 'no-cache' : undefined + await s3Client + .send( + new PutObjectCommand({ + Bucket: bucketName, + Key: path + (PathExt.extname(name) === '' ? '/' : ''), + Body: body, + CacheControl: options ? 'no-cache' : undefined + }) + ) + .then(async () => { + const newFileInfo = await s3Client.send( + new HeadObjectCommand({ + Bucket: bucketName, + Key: path + (PathExt.extname(name) === '' ? '/' : '') + }) + ); + lastModified = newFileInfo.LastModified?.toISOString(); }) - ); + .catch(error => { + console.error('Failed saving or creating the S3 object: ', error); + }); data = { name: name, path: PathExt.join(path, name), - last_modified: new Date().toISOString(), - created: new Date().toISOString(), + last_modified: lastModified ?? new Date().toISOString(), + created: lastModified ?? new Date().toISOString(), content: path.split('.').length === 1 ? [] : body, format: fileFormat as Contents.FileFormat, mimetype: fileMimeType, @@ -312,9 +327,9 @@ export const deleteS3Objects = async ( if (Contents) { await Promise.all( - Contents.map(c => { + Contents.map(async c => { // delete each file with given path - Private.deleteFile(s3Client, bucketName, c.Key!); + return Private.deleteFile(s3Client, bucketName, c.Key!); }) ); } @@ -402,28 +417,14 @@ export const renameS3Objects = async ( await s3Client.send(command); if (Contents) { - // retrieve information of file or directory - const fileContents = await s3Client.send( + // retrieve content of file or directory + const oldFileContents = await s3Client.send( new GetObjectCommand({ Bucket: bucketName, Key: Contents[0].Key! }) ); - - const body = await fileContents.Body?.transformToString(); - - data = { - name: newFileName, - path: newLocalPath, - last_modified: fileContents.LastModified!.toISOString(), - created: '', - content: body ? body : [], - format: fileFormat as Contents.FileFormat, - mimetype: fileMimeType, - size: fileContents.ContentLength!, - writable: true, - type: fileType - }; + const body = await oldFileContents.Body?.transformToString(); const promises = Contents.map(async c => { const remainingFilePath = c.Key!.substring(oldLocalPath.length); @@ -442,13 +443,37 @@ export const renameS3Objects = async ( ); }); await Promise.all(promises); + + let lastModifiedDate = new Date().toISOString(); + if (!isDir) { + // retrieve last modified time for new file, does not apply to remaming directory + const newFileMetadata = await s3Client.send( + new HeadObjectCommand({ + Bucket: bucketName, + Key: newLocalPath + }) + ); + lastModifiedDate = newFileMetadata.LastModified!.toISOString(); + } + + data = { + name: newFileName, + path: newLocalPath.replace(root, ''), + last_modified: lastModifiedDate, + created: '', + content: body ? body : [], + format: fileFormat as Contents.FileFormat, + mimetype: fileMimeType, + size: oldFileContents.ContentLength!, + writable: true, + type: fileType + }; } if (isTruncated) { isTruncated = IsTruncated; } command.input.ContinuationToken = NextContinuationToken; } - return data; }; @@ -479,12 +504,12 @@ export const copyS3Objects = async ( newBucketName?: string ): Promise => { const isDir: boolean = PathExt.extname(path) === ''; + let suffix: string = ''; path = PathExt.join(root, path); toDir = PathExt.join(root, toDir); - name = PathExt.join(toDir, name); - path = isDir ? path + '/' : path; + path = path + (isDir ? '/' : ''); // list contents of path - contents of directory or one file const command = new ListObjectsV2Command({ @@ -500,6 +525,9 @@ export const copyS3Objects = async ( if (Contents) { const promises = Contents.map(c => { + if (!suffix && c.Key!.search('/.emptyFolderPlaceholder') !== -1) { + suffix = '.emptyFolderPlaceholder'; + } const remainingFilePath = c.Key!.substring(path.length); // copy each file from old directory to new location return Private.copyFile( @@ -528,7 +556,7 @@ export const copyS3Objects = async ( const newFileContents = await s3Client.send( new GetObjectCommand({ Bucket: newBucketName ?? bucketName, - Key: name + Key: name + (suffix ? suffix : '') }) ); @@ -672,6 +700,7 @@ namespace Private { Key: PathExt.join(newPath, remainingFilePath) }) ); + console.log('copy: ', PathExt.join(newPath, remainingFilePath)); } /** diff --git a/src/s3contents.ts b/src/s3contents.ts index 9f76579..1b1fc5a 100644 --- a/src/s3contents.ts +++ b/src/s3contents.ts @@ -336,7 +336,6 @@ export class Drive implements Contents.IDrive { oldValue: null, newValue: data }); - return data; } @@ -428,35 +427,29 @@ export class Drive implements Contents.IDrive { ): Promise { let newFileName = PathExt.basename(newLocalPath); - await checkS3Object(this._s3Client, this._name, this._root, newLocalPath) - .then(async () => { - console.log('File name already exists!'); - // construct new incremented name - newFileName = await this.incrementName(newLocalPath, this._name); - }) - .catch(() => { - // function throws error as the file name doesn't exist - console.log("Name doesn't exist!"); - }) - .finally(async () => { - // once the name has been incremented if needed, proceed with the renaming - data = await renameS3Objects( - this._s3Client, - this._name, - this._root, - oldLocalPath, - newLocalPath, - newFileName, - this._registeredFileTypes - ); - }); + try { + await checkS3Object(this._s3Client, this._name, this._root, newLocalPath); + newFileName = await this.incrementName(newLocalPath, this._name); + } catch (error) { + // HEAD request failed for this file name, continue, as name doesn't already exist. + } finally { + data = await renameS3Objects( + this._s3Client, + this._name, + this._root, + oldLocalPath, + newLocalPath, + newFileName, + this._registeredFileTypes + ); + } + Contents.validateContentsModel(data); this._fileChanged.emit({ type: 'rename', oldValue: { path: oldLocalPath }, newValue: data }); - Contents.validateContentsModel(data); return data; } @@ -534,12 +527,12 @@ export class Drive implements Contents.IDrive { options ); + Contents.validateContentsModel(data); this._fileChanged.emit({ type: 'save', oldValue: null, newValue: data }); - Contents.validateContentsModel(data); return data; } @@ -605,12 +598,12 @@ export class Drive implements Contents.IDrive { this._registeredFileTypes ); + Contents.validateContentsModel(data); this._fileChanged.emit({ type: 'new', oldValue: null, newValue: data }); - Contents.validateContentsModel(data); return data; } @@ -773,7 +766,7 @@ export class Drive implements Contents.IDrive { root = PathExt.removeSlash(PathExt.normalize(root)); // check if directory exists within bucket try { - checkS3Object(this._s3Client, this._name, root); + await checkS3Object(this._s3Client, this._name, root); // the directory exists, root is formatted correctly return root; } catch (error) {