Skip to content

Commit

Permalink
Refactor copyFile in StorageLayer (#4351)
Browse files Browse the repository at this point in the history
* multipart and firebase.ts integration

* update gcloud.ts

* refactor persistence out into StorageEmulator

* fix lint and method references

* fix tests

* upload.ts

* update callers of UploadService and tests

* more tests

* more tests

* md -> metadata

* address pr comments

* lint

* fix tests

* fix tests

* stash

* more integration tests, fix upload bug

* more fixing

* fix remaining

* lint

* metadata

* deleteobject

* list

* get

* fix tests

* lint

* address pr comments

* lint

* restructure

* cleanup

* fix merge

* remove skipAuth param from StorageLayer

* rename to adminStorageLayer to make StorageLayer type explicit

* fix imports

* change to use passthrough logic

* share state across storagelayers

* fix tests

* fix import

* address pr comments

* lint

* revert

* pr comments

* more cleanup

* fix tests

* remove only

* move internal tests to new file

* internals test file

* refactor gcloud to use same list method in StorageLayer

* fix imports

* delete object

* lint

* copyfile

* public -> private

* override auth

* lint
  • Loading branch information
tonyjhuang authored and tohhsinpei committed Mar 23, 2022
1 parent af594ec commit ecd59d1
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 36 deletions.
38 changes: 21 additions & 17 deletions src/emulator/storage/apis/gcloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,27 +290,31 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router {
gcloudStorageAPI.post(
"/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId",
(req, res, next) => {
const md = adminStorageLayer.getMetadata(req.params.bucketId, req.params.objectId);

if (!md) {
return sendObjectNotFound(req, res);
}

if (req.params.method === "rewriteTo" && req.query.rewriteToken) {
// Don't yet support multi-request copying
return next();
}

const metadata = adminStorageLayer.copyFile(
md,
req.params.destBucketId,
req.params.destObjectId,
req.body
);

if (!metadata) {
res.sendStatus(400);
return;
let metadata: StoredFileMetadata;
try {
metadata = adminStorageLayer.copyObject({
sourceBucket: req.params.bucketId,
sourceObject: req.params.objectId,
destinationBucket: req.params.destBucketId,
destinationObject: req.params.destObjectId,
incomingMetadata: req.body,
// TODO(tonyjhuang): Until we have a way of validating OAuth tokens passed by
// the GCS sdk or gcloud tool, we must assume all requests have valid admin creds.
// authorization: req.header("authorization")
authorization: "Bearer owner",
});
} catch (err) {
if (err instanceof NotFoundError) {
return sendObjectNotFound(req, res);
}
if (err instanceof ForbiddenError) {
return res.sendStatus(403);
}
throw err;
}

const resource = new CloudStorageObjectMetadata(metadata);
Expand Down
59 changes: 40 additions & 19 deletions src/emulator/storage/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ export type DeleteDownloadTokenRequest = {
authorization?: string;
};

/** Parsed request object for {@link StorageLayer#copyObject}. */
export type CopyObjectRequest = {
sourceBucket: string;
sourceObject: string;
destinationBucket: string;
destinationObject: string;
incomingMetadata?: IncomingMetadata;
authorization?: string;
};

export class StorageLayer {
constructor(
private _projectId: string,
Expand Down Expand Up @@ -170,7 +180,7 @@ export class StorageLayer {
return { metadata: metadata!, data: this.getBytes(request.bucketId, request.decodedObjectId)! };
}

public getMetadata(bucket: string, object: string): StoredFileMetadata | undefined {
private getMetadata(bucket: string, object: string): StoredFileMetadata | undefined {
const key = this.path(bucket, object);
const val = this._files.get(key);

Expand Down Expand Up @@ -319,31 +329,39 @@ export class StorageLayer {
return metadata;
}

public copyFile(
sourceFile: StoredFileMetadata,
destinationBucket: string,
destinationObject: string,
incomingMetadata?: IncomingMetadata
): StoredFileMetadata {
const filePath = this.path(destinationBucket, destinationObject);

this._persistence.deleteFile(filePath, /* failSilently = */ true);
public copyObject({
sourceBucket,
sourceObject,
destinationBucket,
destinationObject,
incomingMetadata,
authorization,
}: CopyObjectRequest): StoredFileMetadata {
if (!this._adminCredsValidator.validate(authorization)) {
throw new ForbiddenError();
}
const sourceMetadata = this.getMetadata(sourceBucket, sourceObject);
if (!sourceMetadata) {
throw new NotFoundError();
}
const sourceBytes = this.getBytes(sourceBucket, sourceObject) as Buffer;

const bytes = this.getBytes(sourceFile.bucket, sourceFile.name) as Buffer;
this._persistence.appendBytes(filePath, bytes);
const destinationFilePath = this.path(destinationBucket, destinationObject);
this._persistence.deleteFile(destinationFilePath, /* failSilently = */ true);
this._persistence.appendBytes(destinationFilePath, sourceBytes);

const newMetadata: IncomingMetadata = {
...sourceFile,
metadata: sourceFile.customMetadata,
...sourceMetadata,
metadata: sourceMetadata.customMetadata,
...incomingMetadata,
};
if (
sourceFile.downloadTokens.length &&
sourceMetadata.downloadTokens.length &&
// Only copy download tokens if we're not overwriting any custom metadata
!(incomingMetadata?.metadata && Object.keys(incomingMetadata?.metadata).length)
) {
if (!newMetadata.metadata) newMetadata.metadata = {};
newMetadata.metadata.firebaseStorageDownloadTokens = sourceFile.downloadTokens.join(",");
newMetadata.metadata.firebaseStorageDownloadTokens = sourceMetadata.downloadTokens.join(",");
}
if (newMetadata.metadata) {
// Convert null metadata values to empty strings
Expand All @@ -364,11 +382,14 @@ export class StorageLayer {
customMetadata: newMetadata.metadata,
},
this._cloudFunctions,
bytes,
sourceBytes,
incomingMetadata
);
const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(filePath));
this._files.set(filePath, file);
const file = new StoredFile(
copiedFileMetadata,
this._persistence.getDiskPath(destinationFilePath)
);
this._files.set(destinationFilePath, file);

this._cloudFunctions.dispatch("finalize", new CloudStorageObjectMetadata(file.metadata));
return file.metadata;
Expand Down

0 comments on commit ecd59d1

Please sign in to comment.