Comparing version 0.8.0 to 0.8.1
@@ -20,3 +20,3 @@ 'use strict'; | ||
if (req.query.snapshot) { | ||
blob.setSnapshotDate(req.query.params.snapshot); | ||
blob.setSnapshotDate(req.query.snapshot); | ||
} | ||
@@ -34,5 +34,3 @@ storageManager.getBlob(containerName, blob) | ||
// Fixme: Depending on whether the blob refers to a virtual directory or a snapshot the path differs. | ||
// We need the same mechanism that we use for storage url (see env.storageUrl) for workspace directory. | ||
const fullPath = path.join(env.localStoragePath, containerName, blobName); | ||
const fullPath = env.diskStorageUri(containerName, blob); | ||
const readStream = fs.createReadStream(fullPath, { | ||
@@ -59,3 +57,3 @@ flags: 'r', | ||
} else { | ||
request(this._addRequestHeader(env.storageUrl(env.port, containerName, blob), range)) | ||
request(this._addRequestHeader(env.webStorageUri(env.port, containerName, blob), range)) | ||
.on('response', (staticResponse) => { | ||
@@ -62,0 +60,0 @@ result.httpProps['Content-Length'] = staticResponse.headers['content-length']; |
'use strict'; | ||
const storageManager = require('./../StorageManager'), | ||
Blob = require('./../model/Blob'), | ||
ResponseHeader = require('./../model/ResponseHeader'); | ||
@@ -11,3 +12,7 @@ | ||
process(req, res, containerName, blobName) { | ||
storageManager.getBlobMetadata(containerName, blobName) | ||
const blob = new Blob(blobName, req.headers); | ||
if (req.query.snapshot) { | ||
blob.setSnapshotDate(req.query.snapshot); | ||
} | ||
storageManager.getBlobMetadata(containerName, blob) | ||
.then((result) => { | ||
@@ -14,0 +19,0 @@ res.set(new ResponseHeader(result.httpProps, result.metaProps)); |
'use strict'; | ||
const storageManager = require('./../StorageManager'), | ||
Blob = require('./../model/Blob'), | ||
ResponseHeader = require('./../model/ResponseHeader'); | ||
@@ -11,3 +12,7 @@ | ||
process(req, res, containerName, blobName) { | ||
storageManager.getBlobProperties(containerName, blobName) | ||
const blob = new Blob(blobName, req.headers); | ||
if (req.query.snapshot) { | ||
blob.setSnapshotDate(req.query.snapshot); | ||
} | ||
storageManager.getBlobProperties(containerName, blob) | ||
.then((result) => { | ||
@@ -14,0 +19,0 @@ const optionalProps = { |
@@ -18,3 +18,3 @@ 'use strict'; | ||
const model = this._createModel(result.pageRanges); | ||
res.set(new ResponseHeader({ 'x-ms-blob-content-length': result.httpProps['Content-Length'] })); | ||
res.set(new ResponseHeader({ 'x-ms-blob-content-length': result.size })); | ||
res.status(200).send(model.toString()); | ||
@@ -21,0 +21,0 @@ }) |
@@ -21,3 +21,3 @@ 'use strict'; | ||
xmlDoc = xmlDoc.replace(/\>[\s]+\</g, '><'); | ||
res.set(new ResponseHeader({ 'Content-Type': 'application/xml', 'Content-Length': xmlDoc.length })); | ||
res.set(new ResponseHeader({ 'Content-Type': 'application/xml' })); | ||
res.status(200).send(xmlDoc); | ||
@@ -24,0 +24,0 @@ }) |
@@ -18,3 +18,3 @@ 'use strict'; | ||
res.set(new ResponseHeader(response)); | ||
res.status(201).send('Created'); | ||
res.status(201).send(); | ||
}) | ||
@@ -21,0 +21,0 @@ .catch((e) => { |
'use strict'; | ||
const path = require('path'), | ||
const utils = require('./utils'), | ||
path = require('path'), | ||
BbPromise = require('bluebird'), | ||
@@ -36,3 +37,13 @@ fs = BbPromise.promisifyAll(require("fs-extra")); | ||
storageUrl(port, container, blob) { | ||
/** | ||
* Based on the container name and blob it creates the according URI that is served by Azurite's web interface. | ||
* | ||
* @param {number} port | ||
* @param {string} containerName | ||
* @param {model.Blob} blob | ||
* @returns | ||
* | ||
* @memberof Environment | ||
* */ | ||
webStorageUri(port, containerName, blob) { | ||
return (blob.isVirtualDirectory() && !blob.isSnapshot()) | ||
@@ -42,6 +53,37 @@ ? `http://localhost:${port}/blobs/${this.virtualDirUri}/${container}/${blob.publicName()}` | ||
? `http://localhost:${port}/blobs/${this.snapshotUri}/${container}/${blob.publicName()}` | ||
: `http://localhost:${port}/blobs/${container}/${blob.name}`; | ||
: `http://localhost:${port}/blobs/${containerName}/${blob.name}`; | ||
} | ||
/** | ||
* Based on the blob name it creates the full path to the location on disk. | ||
* | ||
* Virtual directories are stored in a special folder that is not accessible through the Standard REST API. | ||
* This is to make sure that not special characters or words need to be reserved in the regular blob workspace. | ||
* Since virtual directories contain trailing slashes (which are invalid filename characters) we store the | ||
* Base64 representation on disk. | ||
* | ||
* Snapshots are also stored in a special folder since we encode the snapshot date into the name as <blobname>-<snapshotId>. | ||
* | ||
* @param {string} containerName | ||
* @param {model.Blob} blob | ||
* @returns Full path on disk | ||
* | ||
* @memberof StorageManager | ||
*/ | ||
diskStorageUri(containerName, blob) { | ||
let containerPath; | ||
if (blob.isVirtualDirectory()) { | ||
containerPath = path.join(this.virtualDirPath, containerName); | ||
} else if (blob.isSnapshot()) { | ||
containerPath = path.join(this.snapshotPath, containerName); | ||
} else { | ||
containerPath = path.join(this.localStoragePath, containerName); | ||
} | ||
const blobPath = path.join(containerPath, blob.publicName()); | ||
return (blob.isVirtualDirectory()) | ||
? blobPath | ||
: utils.escapeBlobDelimiter(blobPath); | ||
} | ||
} | ||
module.exports = new Environment(); |
@@ -26,3 +26,3 @@ 'use strict'; | ||
this.httpProps['Last-Modified'] = httpHeader['Last-Modified'] || new Date().toGMTString(); | ||
this.httpProps.ETag = httpHeader.ETag || 1; | ||
this.httpProps.ETag = httpHeader.ETag || 0; | ||
this.httpProps['Content-Length'] = httpHeader['Content-Length'] || httpHeader['content-length']; | ||
@@ -29,0 +29,0 @@ // x-ms-* attributes have precedence over according HTTP-Headers |
'use strict'; | ||
const env = require('./env'), | ||
utils = require('./utils'), | ||
path = require('path'), | ||
@@ -144,2 +145,3 @@ BbPromise = require('bluebird'), | ||
delete blob.httpProps['Content-Length']; | ||
if (blobResult.length === 0) { | ||
@@ -166,3 +168,3 @@ const newBlob = coll.insert({ | ||
} | ||
return fs.outputFileAsync(this._getStoragePath(containerName, blob), body, { encoding: blob.httpProps['Content-Encoding'] }) | ||
return fs.outputFileAsync(env.diskStorageUri(containerName, blob), body, { encoding: blob.httpProps['Content-Encoding'] }) | ||
.then(() => { | ||
@@ -205,4 +207,5 @@ return response; | ||
updateBlob.size += body.length; | ||
updateBlob.httpProps.ETag++; | ||
coll.update(updateBlob); | ||
return fs.appendFileAsync(this._getStoragePath(containerName, blob), body, { encoding: updateBlob.httpProps['Content-Encoding'] }) | ||
return fs.appendFileAsync(env.diskStorageUri(containerName, blob), body, { encoding: updateBlob.httpProps['Content-Encoding'] }) | ||
.then(() => { | ||
@@ -218,3 +221,3 @@ return response; | ||
new RootValidator({ | ||
requestBlob: { name: blob.name }, | ||
requestBlob: { publicName: () => { return blob.name } }, | ||
collection: coll | ||
@@ -225,3 +228,3 @@ }) | ||
coll.chain().find({ 'name': { '$eq': blob.name } }).remove(); | ||
return fs.removeAsync(this._getStoragePath(containerName, blob)); | ||
return fs.removeAsync(env.diskStorageUri(containerName, blob)); | ||
}); | ||
@@ -243,3 +246,4 @@ } | ||
const blob = coll.chain() | ||
.find({ 'name': { '$eq': requestBlob.name } }) | ||
// FIXME: This query needs to include snapshot eq true if blob is a snapshot otherwise snapshot eq false | ||
.find({ 'name': { '$eq': requestBlob.publicName() } }) | ||
.data()[0]; | ||
@@ -264,2 +268,3 @@ response.httpProps = blob.httpProps; | ||
let blobs = coll.chain() | ||
// FIXME: Depending on include=snapshots query needs to be adaped, per default snapshots are not shown (snapshot eq false) | ||
.find({ 'name': { '$contains': options.prefix } }) | ||
@@ -300,2 +305,4 @@ .simplesort('name') | ||
.data(); | ||
delete options.blob.httpProps['Content-Length']; | ||
// We only create the blob in DB if it does not already exists. | ||
@@ -341,7 +348,7 @@ if (parentBlobResult.length === 0) { | ||
// Make sure that the parent blob exists on storage. | ||
return fs.ensureFileAsync(this._getStoragePath(containerName, options.blob)) | ||
return fs.ensureFileAsync(env.diskStorageUri(containerName, options.blob)) | ||
.then(() => { | ||
// Writing block to disk. | ||
const blockPath = path.join(env.commitsPath, options.fileName); | ||
return fs.outputFileAsync(this._escapeBlobDelimiter(blockPath), body, { encoding: options.blob.httpProps['Content-Encoding'] }); | ||
return fs.outputFileAsync(utils.escapeBlobDelimiter(blockPath), body, { encoding: options.blob.httpProps['Content-Encoding'] }); | ||
}) | ||
@@ -365,3 +372,3 @@ .then(() => { | ||
validator.run(BlobExistsVal, { | ||
requestBlob: { name: blockName } | ||
requestBlob: { publicName: () => { return blockName } } | ||
}); | ||
@@ -381,2 +388,3 @@ } | ||
delete blob.httpProps['Content-Length']; | ||
if (blobResult.length == 0) { | ||
@@ -409,3 +417,3 @@ const newBlob = coll.insert({ | ||
return new BbPromise((resolve, reject) => { | ||
const destinationStream = fs.createWriteStream(this._getStoragePath(containerName, blob)); | ||
const destinationStream = fs.createWriteStream(env.diskStorageUri(containerName, blob)); | ||
destinationStream | ||
@@ -461,3 +469,3 @@ .on('error', (e) => { | ||
.run(ContainerExistsVal, { containerName: containerName, collection: this.db.getCollection(StorageTables.Containers) }) | ||
.run(BlobExistsVal) | ||
.run(BlobExistsVal, { requestBlob: { publicName: () => { return blobName } } }) | ||
.run(SupportedBlobTypeVal) | ||
@@ -503,3 +511,3 @@ .run(IsOfBlobTypeVal, { blobType: BlobTypes.BlockBlob }); | ||
getBlobMetadata(containerName, blobName) { | ||
getBlobMetadata(containerName, requestBlob) { | ||
return BbPromise.try(() => { | ||
@@ -512,6 +520,6 @@ new RootValidator() | ||
.run(BlobExistsVal, { | ||
requestBlob: { name: blobName }, | ||
requestBlob: requestBlob, | ||
collection: this.db.getCollection(containerName) | ||
}); | ||
const res = this._getCollectionAndBlob(containerName, blobName), | ||
const res = this._getCollectionAndBlob(containerName, requestBlob.publicName()), | ||
blob = res.blob, | ||
@@ -557,5 +565,5 @@ httpProps = blob.httpProps, | ||
getBlobProperties(containerName, blobName) { | ||
getBlobProperties(containerName, requestBlob) { | ||
// For block blobs return values are equal in Azurite | ||
return this.getBlobMetadata(containerName, blobName); | ||
return this.getBlobMetadata(containerName, requestBlob); | ||
} | ||
@@ -663,3 +671,3 @@ | ||
blobPath = path.join(env.localStoragePath, containerName, blob.name), | ||
writeStream = fs.createWriteStream(this._escapeBlobDelimiter(blobPath), { | ||
writeStream = fs.createWriteStream(utils.escapeBlobDelimiter(blobPath), { | ||
flags: 'r+', | ||
@@ -743,3 +751,3 @@ start: startByte, | ||
pageRanges: pageRanges, | ||
httpProps: res.blob.httpProps | ||
size: res.blob.size | ||
}; | ||
@@ -824,3 +832,3 @@ }); | ||
snapshotBlob.metaProps = requestBlob.metaProps; | ||
snapshotBlob.ETag += 1; | ||
snapshotBlob.httpProps.ETag += 1; | ||
snapshotBlob.httpProps['Last-Modified'] = requestBlob.httpProps['Last-Modified']; | ||
@@ -833,3 +841,3 @@ } | ||
.then(() => { | ||
return fs.copyAsync(this._getStoragePath(containerName, requestBlob), destPath); | ||
return fs.copyAsync(env.diskStorageUri(containerName, requestBlob), destPath); | ||
}) | ||
@@ -925,49 +933,4 @@ .then(() => { | ||
} | ||
/** | ||
* Based on the blob name it creates the full path to the location on disk. Virtual directories | ||
* are stored in a special folder that is not accessible through the Standard REST API. This is | ||
* to make sure that not special characters or words need to be reserved in the regular blob workspace. | ||
* Since virtual directories contain trailing slashes (which are invalid filename characters) we store the | ||
* Base64 representation on disk. | ||
* | ||
* @param {any} containerName | ||
* @param {any} blob | ||
* @returns Full path on disk | ||
* | ||
* @memberof StorageManager | ||
*/ | ||
_getStoragePath(containerName, blob) { | ||
let containerPath; | ||
if (blob.isVirtualDirectory()) { | ||
containerPath = path.join(env.virtualDirPath, containerName); | ||
} else if (blob.isSnapshot()) { | ||
containerPath = path.join(env.snapshotPath, containerName); | ||
} else { | ||
containerPath = path.join(env.localStoragePath, containerName); | ||
} | ||
const blobPath = path.join(containerPath, blob.publicName()); | ||
return (blob.isVirtualDirectory()) | ||
? blobPath | ||
: this._escapeBlobDelimiter(blobPath); | ||
} | ||
/** | ||
* Not all allowed delimiters for blob names are valid file names. We thus replace those that are invalid with the valid | ||
* delimiter @ on disk. Note that in our in-memory database and thus for the external interface we still | ||
* use the originally chosen delimiter. | ||
*/ | ||
_escapeBlobDelimiter(blobPath) { | ||
if (process.platform === 'win32') { | ||
const pathWithoutLetter = blobPath.substr(2); | ||
if (pathWithoutLetter === '') { | ||
return blobPath; | ||
} | ||
return (blobPath.substr(0, 2) + pathWithoutLetter.replace(/(::|:|\/|\||\/\/)/g, '@')); | ||
} else { | ||
return blobPath.replace(/(::|:|\||\$)/g, '@'); | ||
} | ||
} | ||
} | ||
module.exports = new StorageManager; |
@@ -23,4 +23,5 @@ 'use strict'; | ||
} | ||
const name = options.requestBlob.name; | ||
const name = options.requestBlob.publicName(); | ||
const coll = options.collection; | ||
// FIXME: The query needs to include snapshot eq true if blob is a snapshot | ||
if (!coll || coll.chain().find({ name: { '$eq': name } }).data().length !== 1) { | ||
@@ -27,0 +28,0 @@ throw new AError(ErrorCodes.BlobNotFound); |
{ | ||
"name": "azurite", | ||
"version": "0.8.0", | ||
"version": "0.8.1", | ||
"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": { |
@@ -13,3 +13,3 @@ 'use strict'; | ||
expect(item).to.have.property('name', 'testItem'); | ||
expect(item.httpProps).to.have.property('ETag', 1); | ||
expect(item.httpProps).to.have.property('ETag', 0); | ||
expect(item.httpProps).to.have.property('Last-Modified'); | ||
@@ -33,3 +33,3 @@ expect(item.httpProps).to.have.property('Content-Type', 'application/octet-stream'); | ||
const item = new StorageItem('testItem', httpHeader); | ||
expect(item.httpProps).to.have.property('ETag', 1); | ||
expect(item.httpProps).to.have.property('ETag', 0); | ||
expect(item.httpProps).to.have.property('Last-Modified'); | ||
@@ -36,0 +36,0 @@ expect(item.httpProps).to.have.property('Content-Type', 'ContentType'); |
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
191288
74
3380