Comparing version 0.9.16 to 0.10.0
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -29,3 +29,11 @@ 'use strict'; | ||
response.addHttpProperty(N.CONTENT_LENGTH, response.proxy.original.size); | ||
response.addHttpProperty(N.COPY_ID, response.proxy.original.copyId); | ||
response.addHttpProperty(N.COPY_STATUS, response.proxy.original.copyStatus); | ||
response.addHttpProperty(N.COPY_COMPLETION_TIME, response.proxy.original.copyCompletionTime); | ||
response.addHttpProperty(N.COPY_STATUS_DESCRIPTION, response.proxy.original.copyStatusDescription); | ||
response.addHttpProperty(N.COPY_PROGRESS, response.proxy.original.copyProgress); | ||
response.addHttpProperty(N.COPY_SOURCE, response.proxy.original.copySource); | ||
response.addHttpProperty(N.INCREMENTAL_COPY, response.proxy.original.incrementalCopy); | ||
response.addHttpProperty(N.SEQUENCE_NUMBER, response.proxy.original.sequenceNumber); | ||
response.addHttpProperty(N.BLOB_COMMITTED_BLOCK_COUNT, response.proxy.original[N.BLOB_COMMITTED_BLOCK_COUNT]); | ||
res.set(response.httpProps); | ||
@@ -32,0 +40,0 @@ res.status(200).send(); |
@@ -81,3 +81,4 @@ 'use strict'; | ||
APPEND_BLOCK: 'AppendBlock', | ||
COPY_BLOB: 'CopyBlob' | ||
COPY_BLOB: 'CopyBlob', | ||
ABORT_COPY_BLOB: 'AbortCopyBlob' | ||
} | ||
@@ -84,0 +85,0 @@ } |
@@ -53,3 +53,3 @@ 'use strict'; | ||
// We prepend a specific character to guarantee unique ids. | ||
// This is neccessary since otherwise snapshot IDs could overlap with blob IDs could overlap with page IDs, .... | ||
// This is neccessary since otherwise snapshot IDs could overlap with block IDs could overlap with block-/append-/page-blob IDs. | ||
blobId(containerName, blobName) { | ||
@@ -56,0 +56,0 @@ return Buffer.from(`A${containerName}${blobName}`, 'utf8').toString('base64'); |
@@ -51,3 +51,4 @@ 'use strict'; | ||
PendingCopyOperation: new ErrorCode('PendingCopyOperation', 409, 'There is currently a pending copy operation.'), | ||
NoPendingCopyOperation: new ErrorCode('NoPendingCopyOperation', 409, 'There is currently no pending copy operation.'), | ||
InvalidBlockList: new ErrorCode('InvalidBlockList', 400, 'The specified block list is invalid.') | ||
} |
@@ -31,3 +31,4 @@ 'use strict'; | ||
snapshotBlob = require('./../actions/SnapshotBlob'), | ||
copyBlob = require('./../actions/CopyBlob'); | ||
copyBlob = require('./../actions/CopyBlob'), | ||
abortCopyBlob = require('./../actions/AbortCopyBlob'); | ||
@@ -38,6 +39,3 @@ | ||
actions[req.azuriteOperation](req.azuriteRequest, res); | ||
// Refactor me: Move this to bin/azurite (exception needs to carry res object), and handle entire exception handling there | ||
}).catch((e) => { | ||
// e.res = res; | ||
// throw e; | ||
res.status(e.statusCode || 500).send(e.message); | ||
@@ -151,2 +149,6 @@ if (!e.statusCode) throw e; | ||
copyBlob.process(request, res); | ||
} | ||
actions[Operations.Blob.ABORT_COPY_BLOB] = (request, res) => { | ||
abortCopyBlob.process(request, res); | ||
} |
@@ -38,2 +38,3 @@ 'use strict'; | ||
BlockListValidation = require('./../validation/BlockList'), | ||
AbortCopyValidation = require('./../validation/AbortCopy'), | ||
CopyStatusValidation = require('./../validation/CopyStatus'); | ||
@@ -43,3 +44,3 @@ | ||
BbPromise.try(() => { | ||
if (req.azuriteOperation === undefined || req.azuriteOperation === Operations.Blob.COPY_BLOB) { | ||
if (req.azuriteOperation === undefined /*|| req.azuriteOperation === Operations.Blob.COPY_BLOB*/) { | ||
res.status(501).send('Not Implemented yet.'); | ||
@@ -277,7 +278,21 @@ return; | ||
validations[Operations.Blob.COPY_BLOB] = (request, valContext) => { | ||
// Source Validation | ||
const sourceBlobProxy = sm._getCopySourceProxy(request); | ||
const ret = sm._getCollectionAndContainer((request.copySourceName()).sourceContainerName), | ||
sourceContainerProxy = ret.containerProxy; | ||
valContext | ||
.run(ContainerExistsVal, { containerProxy: sourceContainerProxy }) | ||
.run(BlobExistsVal, { blobProxy: sourceBlobProxy }); | ||
// Target Validation | ||
valContext | ||
.run(ContainerExistsVal) | ||
.run(BlobExistsVal) | ||
.run(CompatibleBlobTypeVal, { request: { entityType: sourceBlobProxy.original.entityType } }) | ||
.run(ConditionalRequestHeadersVal, { usage: Usage.Write }) | ||
.run(CopyStatusValidation); | ||
} | ||
validations[Operations.Blob.ABORT_COPY_BLOB] = (request, valContext) => { | ||
valContext | ||
.run(AbortCopyValidation); | ||
} |
'use strict'; | ||
const crypto = require('crypto'), | ||
url = require('url'), | ||
EntityType = require('./../Constants').StorageEntityType, | ||
@@ -26,2 +27,3 @@ BlockListType = require('./../Constants').BlockListType, | ||
this.snapshot = false; | ||
this.copyId = req.query.copyid; | ||
// Per default, all (block) blobs will be set to committed by EntityGenerator | ||
@@ -65,20 +67,27 @@ this.commit = true; | ||
/** | ||
* Return the blob's URI of Azurite's internal file system location. | ||
* | ||
* @memberof AzuriteBlobRequest | ||
*/ | ||
copySourceUrl() { | ||
copySourceName() { | ||
if (this.httpProps[N.COPY_SOURCE === undefined]) { | ||
throw new InternalAzuriteError('Request: copySourceUrl was called without copy-source header set.') | ||
} | ||
let uri; | ||
const source = this.httpProps[N.COPY_SOURCE]; | ||
// Same Storage account | ||
if (source.includes('http://127.0.0.1/devstoreaccount1/')) { | ||
source = source.replace('http://127.0.0.1/devstoreaccount1/', ''); | ||
uri = env.diskStorageUri(this); | ||
let source = this.httpProps[N.COPY_SOURCE]; | ||
if (source.includes('http://127.0.0.1:10000/devstoreaccount1/')) { | ||
source = source.replace('http://127.0.0.1:10000/devstoreaccount1/', ''); | ||
} else { // format: /accountName/containerName/blobName | ||
// TODO | ||
} | ||
return result; | ||
source = url.parse(source).pathname; // we ignore query params | ||
const parts = source.split('/'), | ||
containerName = parts[0]; | ||
parts.splice(0, 1); | ||
const blobName = decodeURIComponent(parts.join('/')); // // unicode characters in http headers are encoded! | ||
const query = url.parse(blobName).query | ||
let date = undefined; | ||
if (query !== null && query !== '') { | ||
date = query.split('=')[1] | ||
} | ||
return { | ||
sourceContainerName: containerName, | ||
sourceBlobName: blobName, | ||
date: date | ||
}; | ||
} | ||
@@ -85,0 +94,0 @@ } |
@@ -44,3 +44,6 @@ 'use strict'; | ||
COPY_ID: 'x-ms-copy-id', | ||
COPY_STATUS_DESCRIPTION: 'x-ms-copy-status-description', | ||
COPY_PROGRESS: 'x-ms-copy-progress', | ||
INCREMENTAL_COPY: 'x-ms-incremental-copy', | ||
COPY_DESTINATION_SNAPSHOT: 'x-ms-copy-destination-snapshot', | ||
// Append Blob specific attributes | ||
@@ -47,0 +50,0 @@ BLOB_CONDITION_MAX_SIZE: 'x-ms-blob-condition-maxsize', |
@@ -62,3 +62,6 @@ 'use strict'; | ||
req.azuriteOperation = Operations.Blob.SET_BLOB_PROPERTIES; | ||
} else if (req.headers['x-ms-copy-source'] !== undefined) { | ||
} else if (req.query.comp === 'copy') { | ||
req.azuriteOperation = Operations.Blob.ABORT_COPY_BLOB; | ||
} | ||
else if (req.headers['x-ms-copy-source'] !== undefined) { | ||
req.azuriteOperation = Operations.Blob.COPY_BLOB; | ||
@@ -65,0 +68,0 @@ } else { |
@@ -102,2 +102,3 @@ 'use strict'; | ||
blobProxy = this._createOrUpdateBlob(coll, request); | ||
this._clearCopyMetaData(blobProxy); | ||
return fs.outputFile(request.uri, request.body, { encoding: request.httpProps[N.CONTENT_ENCODING] }) | ||
@@ -178,3 +179,3 @@ .then(() => { | ||
}); | ||
} | ||
} | ||
const coll = this.db.getCollection(request.containerName); | ||
@@ -263,2 +264,3 @@ const blobs = coll.chain() | ||
blobProxy.original.size = totalSize; | ||
this._clearCopyMetaData(blobProxy); | ||
coll.update(blobProxy.release()); | ||
@@ -313,2 +315,3 @@ resolve(new AzuriteResponse({ proxy: blobProxy })); | ||
request.httpProps[N.CONTENT_MD5] ? blobProxy.original.md5 = request.httpProps[N.CONTENT_MD5] : request.calculateContentMd5(); | ||
this._clearCopyMetaData(blobProxy); | ||
coll.update(blobProxy.release()); | ||
@@ -565,31 +568,42 @@ const response = new AzuriteResponse({ proxy: blobProxy }); | ||
copyBlob(request) { | ||
const source = request.copySourceUrl(); | ||
const sourceProxy = this._getCopySourceProxy(request); | ||
let from = null, | ||
to = null; | ||
// TODO: from local storage format is http://127.0.0.1:10000/devstoreaccount1/<container>/<blob> | ||
// which is identical to external format => fix | ||
if (source.type === 'external') { | ||
from = req({ url: source.uri }); | ||
} | ||
if (source.type === 'internal') { | ||
from = fs.createReadStream(source.uri); | ||
// TODO: if blob type is block also copy committed blocks | ||
} | ||
to = fs.createWriteStream(env.diskStorageUri(request)); | ||
from = fs.createReadStream(sourceProxy.original.uri); | ||
// TODO: if blob type is block also copy committed blocks | ||
to = fs.createWriteStream(env.diskStorageUri(request.id)); | ||
from.pipe(to); | ||
const { coll, blobProxySource } = this._getCollectionAndBlob(request.containerName, request.blobName); | ||
const blobProxyDestination = StorageEntityGenerator.clone(blobProxySource); | ||
const coll = this.db.getCollection(request.containerName), | ||
blobProxyDestination = this._createOrUpdateBlob(coll, request), | ||
copyId = uuidv4(); | ||
blobProxyDestination.original.copyStatus = CopyStatus.PENDING; | ||
const copyId = uuidv4(); | ||
blobProxyDestination.original.copyStatusDescription = ''; | ||
blobProxyDestination.original.copyId = copyId; | ||
CopyOperationsManager.add(copyId, from, to, env.diskStorageUri(request)); | ||
CopyOperationsManager.add(copyId, from, to, env.diskStorageUri(request.id)); | ||
let bytesCopied = 0; | ||
to.on('finish', () => { | ||
if (blobProxyDestination.original.copyStatus !== CopyStatus.FAILED) { | ||
blobProxyDestination.original.completionTime = new Date().toGMTString(); | ||
blobProxyDestination.original.copyCompletionTime = new Date().toGMTString(); | ||
blobProxyDestination.original.copyStatus = CopyStatus.SUCCESS; | ||
if (Object.keys(request.metaProps).length > 0) { | ||
blobProxyDestination.original.metaProps = request.metaProps; | ||
delete blobProxyDestination.original.copyStatusDescription; | ||
blobProxyDestination.original.copySource = sourceProxy.original.uri; | ||
const { sourceContainerName, sourceBlobName } = request.copySourceName(); | ||
// encode blobname in case there are unicode characters which are not supported by http headers per default | ||
blobProxyDestination.original.copySource = `http://localhost/devstoreaccount1/${sourceContainerName}/${encodeURIComponent(sourceBlobName)}`; | ||
blobProxyDestination.original.incrementalCopy = false; | ||
blobProxyDestination.original.size = sourceProxy.original.size; | ||
blobProxyDestination.original.entityType = sourceProxy.original.entityType; | ||
blobProxyDestination.original.md5 = sourceProxy.original.md5; | ||
blobProxyDestination.original.metaProps = (Object.keys(request.metaProps).length > 0) | ||
? request.metaProps | ||
: sourceProxy.original.metaProps; | ||
if (sourceProxy.original.entityType === StorageEntityType.PageBlob) { | ||
blobProxyDestination.original.sequenceNumber = sourceProxy.original.sequenceNumber; | ||
} | ||
if (sourceProxy.original.entityType === StorageEntityType.AppendBlob) { | ||
blobProxyDestination[N.BLOB_COMMITTED_BLOCK_COUNT] = sourceProxy.original[N.BLOB_COMMITTED_BLOCK_COUNT]; | ||
} | ||
CopyOperationsManager.clear(copyId); | ||
@@ -599,4 +613,9 @@ coll.update(blobProxyDestination.release()); | ||
}); | ||
from.on('data', (chunk) => { | ||
bytesCopied += chunk.length; | ||
blobProxyDestination.original.copyProgress = `${bytesCopied}/${sourceProxy.original.size}`; | ||
}); | ||
to.on('error', (err) => { | ||
blobProxyDestination.original.copyStatus = CopyStatus.FAILED; | ||
blobProxyDestination.original.copyStatusDescription = err.message; | ||
blobProxyDestination.original.completionTime = new Date().toGMTString(); | ||
@@ -611,2 +630,9 @@ CopyOperationsManager.clear(copyId); | ||
abortCopyBlob(request) { | ||
return CopyOperationsManager.cancel(request.copyId) | ||
.then(() => { | ||
return new AzuriteResponse(); | ||
}); | ||
} | ||
_createOrUpdateBlob(coll, request) { | ||
@@ -694,4 +720,33 @@ const blob = coll.chain().find({ 'id': { '$eq': request.id } }).data(); | ||
} | ||
_getCopySourceProxy(request) { | ||
// const { sourceContainerName, sourceBlobName, date } = request.copySourceName(); | ||
const resp = request.copySourceName(), | ||
sourceContainerName = resp.sourceContainerName, | ||
sourceBlobName = resp.sourceBlobName, | ||
date = resp.date; | ||
if (date !== undefined) { | ||
const { coll, blobProxy } = this._getCollectionAndBlob(sourceContainerName, env.snapshotId(sourceContainerName, sourceBlobName, date)); | ||
if (blobProxy) { | ||
return blobProxy; | ||
} | ||
} | ||
const { coll, blobProxy } = this._getCollectionAndBlob(sourceContainerName, env.blobId(sourceContainerName, sourceBlobName)); | ||
if (blobProxy) { | ||
return blobProxy; | ||
} | ||
} | ||
_clearCopyMetaData(proxy) { | ||
delete proxy.original.copyId; | ||
delete proxy.original.copyStatus; | ||
delete proxy.original.copyCompletionTime; | ||
delete proxy.original.copyStatusDescription; | ||
delete proxy.original.copyProgress; | ||
delete proxy.original.copySource; | ||
delete proxy.original.incrementalCopy; | ||
delete proxy.original.copyDestinationSnapshot; | ||
} | ||
} | ||
module.exports = new StorageManager; |
@@ -6,9 +6,7 @@ 'use strict'; | ||
* Since the validation is synchronous / single-threaded we can be certain about the exact state of the entire | ||
* application after @see ValidationContext exits. | ||
* application before and after @see ValidationContext exits. | ||
* | ||
* In case a validation fails an according @see AzuriteException is thrown which is then expected | ||
* to be processed by the responsible API Handler. | ||
* In case a validation fails an according @see AzuriteException is thrown which is then processed | ||
* by the validation middleware module middleware/validation.js | ||
* | ||
* A validation module should only need the Azurite request object and the container/blob-proxy1 | ||
* | ||
* @class ValidationContext | ||
@@ -15,0 +13,0 @@ */ |
{ | ||
"name": "azurite", | ||
"version": "0.9.16", | ||
"version": "0.10.0", | ||
"description": "A lightweight server clone of Azure Blob Storage that simulates most of the commands supported by it with minimal dependencies.", | ||
@@ -5,0 +5,0 @@ "scripts": { |
@@ -186,6 +186,6 @@ # Azurite | ||
- Copy Blob [IN-PROGRESS] | ||
- Copy Blob [DONE] | ||
Copies a source blob to a destination blob in this storage account or in another storage account. | ||
- Abort Copy Blob [TODO] | ||
- Abort Copy Blob [DONE] | ||
Aborts a pending Copy Blob operation, and leaves a destination blob with zero length and full metadata. | ||
@@ -192,0 +192,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
249399
102
4490