import { stat } from "fs" ; import Loki from "lokijs" ; import uuid from "uuid/v4" ; import IGCExtentProvider from "../../common/IGCExtentProvider" ; import { convertDateTimeStringMsTo7Digital , rimrafAsync } from "../../common/utils/utils" ; import { newEtag } from "../../common/utils/utils" ; import { validateReadConditions } from "../conditions/ReadConditionalHeadersValidator" ; import { validateSequenceNumberWriteConditions , validateWriteConditions } from "../conditions/WriteConditionalHeadersValidator" ; import StorageErrorFactory from "../errors/StorageErrorFactory" ; import * as Models from "../generated/artifacts/models" ; import Context from "../generated/Context" ; import PageBlobRangesManager from "../handlers/PageBlobRangesManager" ; import BlobLeaseAdapter from "../lease/BlobLeaseAdapter" ; import BlobLeaseSyncer from "../lease/BlobLeaseSyncer" ; import BlobReadLeaseValidator from "../lease/BlobReadLeaseValidator" ; import BlobWriteLeaseSyncer from "../lease/BlobWriteLeaseSyncer" ; import BlobWriteLeaseValidator from "../lease/BlobWriteLeaseValidator" ; import ContainerDeleteLeaseValidator from "../lease/ContainerDeleteLeaseValidator" ; import ContainerLeaseAdapter from "../lease/ContainerLeaseAdapter" ; import ContainerLeaseSyncer from "../lease/ContainerLeaseSyncer" ; import ContainerReadLeaseValidator from "../lease/ContainerReadLeaseValidator" ; import { ILease } from "../lease/ILeaseState" ; import LeaseFactory from "../lease/LeaseFactory" ; import { DEFAULT_LIST_BLOBS_MAX_RESULTS , DEFAULT_LIST_CONTAINERS_MAX_RESULTS , MAX_APPEND_BLOB_BLOCK_COUNT } from "../utils/constants" ; import BlobReferredExtentsAsyncIterator from "./BlobReferredExtentsAsyncIterator" ; import IBlobMetadataStore , { AcquireBlobLeaseResponse , AcquireContainerLeaseResponse , BlobId , BlobModel , BlockModel , BreakBlobLeaseResponse , BreakContainerLeaseResponse , ChangeBlobLeaseResponse , ChangeContainerLeaseResponse , ContainerModel , CreateSnapshotResponse , GetBlobPropertiesRes , GetContainerAccessPolicyResponse , GetContainerPropertiesResponse , GetPageRangeResponse , IContainerMetadata , IExtentChunk , PersistencyBlockModel , ReleaseBlobLeaseResponse , ReleaseContainerLeaseResponse , RenewBlobLeaseResponse , RenewContainerLeaseResponse , ServicePropertiesModel , SetContainerAccessPolicyOptions } from "./IBlobMetadataStore" ; export class export default class LokiBlobMetadataStore implements IBlobMetadataStore , IGCExtentProvider { private readonly db : Loki ; private initialized : boolean = false ; private closed : boolean = true ; private readonly SERVICES_COLLECTION = "$SERVICES_COLLECTION$" ; private readonly CONTAINERS_COLLECTION = "$CONTAINERS_COLLECTION$" ; private readonly BLOBS_COLLECTION = "$BLOBS_COLLECTION$" ; private readonly BLOCKS_COLLECTION = "$BLOCKS_COLLECTION$" ; private readonly pageBlobRangesManager = new PageBlobRangesManager ( ) ; public constructor ( public readonly lokiDBPath ) { this . db = new Loki ( lokiDBPath , { autosave : true , autosaveInterval : 5000 } ) ; } public isInitialized ( ) { return this . initialized ; } public isClosed ( ) { return this . closed ; } public async init ( ) { await new Promise < void > ( ( resolve , reject ) => { stat ( this . lokiDBPath , ( statError , stats ) => { if ( ! statError ) { this . db . loadDatabase ( { } , ( dbError ) => { if ( dbError ) { reject ( dbError ) ; } else { resolve ( ) ; } } ) ; } else { resolve ( ) ; } } ) ; } ) ; let servicePropertiesColl = this . db . getCollection ( this . SERVICES_COLLECTION ) ; if ( servicePropertiesColl === null ) { servicePropertiesColl = this . db . addCollection ( this . SERVICES_COLLECTION , { unique : [ "accountName" ] } ) ; } if ( this . db . getCollection ( this . CONTAINERS_COLLECTION ) === null ) { this . db . addCollection ( this . CONTAINERS_COLLECTION , { indices : [ "accountName" , "name" ] } ) ; } if ( this . db . getCollection ( this . BLOBS_COLLECTION ) === null ) { this . db . addCollection ( this . BLOBS_COLLECTION , { indices : [ "accountName" , "containerName" , "name" , "snapshot" ] } ) ; } if ( this . db . getCollection ( this . BLOCKS_COLLECTION ) === null ) { this . db . addCollection ( this . BLOCKS_COLLECTION , { indices : [ "accountName" , "containerName" , "blobName" , "name" ] } ) ; } await new Promise < void > ( ( resolve , reject ) => { this . db . saveDatabase ( ( err ) => { if ( err ) { reject ( err ) ; } else { resolve ( ) ; } } ) ; } ) ; this . initialized = true ; this . closed = false ; } returns { Promise < void > } memberof public async close ( ) { await new Promise < void > ( ( resolve , reject ) => { this . db . close ( ( err ) => { if ( err ) { reject ( err ) ; } else { resolve ( ) ; } } ) ; } ) ; this . closed = true ; } returns { Promise < void > } memberof public async clean ( ) { if ( this . isClosed ( ) ) { await rimrafAsync ( this . lokiDBPath ) ; return ; } throw new Error ( `Cannot clean LokiBlobMetadataStore, it's not closed.` ) ; } public iteratorExtents ( ) { return new BlobReferredExtentsAsyncIterator ( this ) ; } param { ServicePropertiesModel } serviceProperties returns { Promise < ServicePropertiesModel > } memberof public async setServiceProperties ( context , serviceProperties ) { const coll = this . db . getCollection ( this . SERVICES_COLLECTION ) ; const doc = coll . by ( "accountName" , serviceProperties . accountName ) ; if ( doc ) { doc . cors = serviceProperties . cors === undefined ? doc . cors : serviceProperties . cors ; doc . hourMetrics = serviceProperties . hourMetrics === undefined ? doc . hourMetrics : serviceProperties . hourMetrics ; doc . logging = serviceProperties . logging === undefined ? doc . logging : serviceProperties . logging ; doc . minuteMetrics = serviceProperties . minuteMetrics === undefined ? doc . minuteMetrics : serviceProperties . minuteMetrics ; doc . defaultServiceVersion = serviceProperties . defaultServiceVersion === undefined ? doc . defaultServiceVersion : serviceProperties . defaultServiceVersion ; doc . deleteRetentionPolicy = serviceProperties . deleteRetentionPolicy === undefined ? doc . deleteRetentionPolicy : serviceProperties . deleteRetentionPolicy ; doc . staticWebsite = serviceProperties . staticWebsite === undefined ? doc . staticWebsite : serviceProperties . staticWebsite ; return coll . update ( doc ) ; } else { return coll . insert ( serviceProperties ) ; } } param { string } account returns { Promise < ServicePropertiesModel | undefined > } memberof public async getServiceProperties ( context , account ) { const coll = this . db . getCollection ( this . SERVICES_COLLECTION ) ; const doc = coll . by ( "accountName" , account ) ; return doc ? doc : undefined ; } param { string } account param { string } prefix param { number } maxResults param { string } marker returns { ( Promise < [ ContainerModel [ ] , string | undefined ] > ) } memberof public async listContainers ( context , account , prefix = "" , maxResults = DEFAULT_LIST_CONTAINERS_MAX_RESULTS , marker = "" ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const query = prefix === "" ? { name : { $gt : marker } , accountName : account } : { name : { $regex : `^${ this . escapeRegex ( prefix ) }` , $gt : marker } , accountName : account } ; const query2 = { name : { $gt : marker } } ; const docs = coll . chain ( ) . find ( query ) . find ( query2 ) . simplesort ( "name" ) . limit ( maxResults + 1 ) . data ( ) ; if ( docs . length <= maxResults ) { return [ docs . map ( ( doc ) => { return LeaseFactory . createLeaseState ( new ContainerLeaseAdapter ( doc ) , context ) . sync ( new ContainerLeaseSyncer ( doc ) ) ; } ) , undefined ] ; } else { const nextMarker = docs [ docs . length - 2 ] . name ; docs . pop ( ) ; return [ docs . map ( ( doc ) => { return LeaseFactory . createLeaseState ( new ContainerLeaseAdapter ( doc ) , context ) . sync ( new ContainerLeaseSyncer ( doc ) ) ; } ) , nextMarker ] ; } } param { ContainerModel } container returns { Promise < ContainerModel > } memberof public async createContainer ( context , container ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = coll . findOne ( { accountName : container . accountName , name : container . name } ) ; if ( doc ) { const requestId = context ? context . contextId : undefined ; throw StorageErrorFactory . getContainerAlreadyExists ( requestId ) ; } return coll . insert ( container ) ; } param { string } account param { string } container param { Context } context param { Models . LeaseAccessConditions } leaseAccessConditions returns { Promise < GetContainerPropertiesResponse > } memberof public async getContainerProperties ( context , account , container , leaseAccessConditions ? ) { const doc = await this . getContainerWithLeaseUpdated ( account , container , context ) ; new ContainerReadLeaseValidator ( leaseAccessConditions ) . validate ( new ContainerLeaseAdapter ( doc ) , context ) ; const res = { name : container , properties : doc . properties , metadata : doc . metadata } ; return res ; } param { string } account param { string } container param { Context } context param { Models . ContainerDeleteMethodOptionalParams } options returns { Promise < void > } memberof public async deleteContainer ( context , account , container , options = { } ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = await this . getContainerWithLeaseUpdated ( account , container , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } new ContainerDeleteLeaseValidator ( options . leaseAccessConditions ) . validate ( new ContainerLeaseAdapter ( doc ) , context ) ; coll . remove ( doc ) ; const blobColl = this . db . getCollection ( this . BLOBS_COLLECTION ) ; blobColl . findAndRemove ( { accountName : account , containerName : container } ) ; const blockColl = this . db . getCollection ( this . BLOCKS_COLLECTION ) ; blockColl . findAndRemove ( { accountName : account , containerName : container } ) ; } param { Context } context param { string } account param { string } container param { Date } lastModified param { string } etag param { IContainerMetadata } metadata param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < void > } memberof public async setContainerMetadata ( context , account , container , lastModified , etag , metadata ? , leaseAccessConditions ? , modifiedAccessConditions ? ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = await this . getContainerWithLeaseUpdated ( account , container , context , false ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } new ContainerReadLeaseValidator ( leaseAccessConditions ) . validate ( new ContainerLeaseAdapter ( doc ) , context ) ; doc . properties . lastModified = lastModified ; doc . properties . etag = etag ; doc . metadata = metadata ; return coll . update ( doc ) ; } param { string } account param { string } container param { Context } context param { Models . LeaseAccessConditions } leaseAccessConditions returns { Promise < GetContainerAccessPolicyResponse > } memberof public async getContainerACL ( context , account , container , leaseAccessConditions ? ) { const doc = await this . getContainerWithLeaseUpdated ( account , container , context ) ; new ContainerReadLeaseValidator ( leaseAccessConditions ) . validate ( new ContainerLeaseAdapter ( doc ) , context ) ; const res = { properties : doc . properties , containerAcl : doc . containerAcl } ; return res ; } param { string } account param { string } container param { SetContainerAccessPolicyOptions } setAclModel param { Context } context returns { Promise < void > } memberof public async setContainerACL ( context , account , container , setAclModel ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = await this . getContainerWithLeaseUpdated ( account , container , context , false ) ; validateWriteConditions ( context , setAclModel . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } new ContainerReadLeaseValidator ( setAclModel . leaseAccessConditions ) . validate ( new ContainerLeaseAdapter ( doc ) , context ) ; doc . properties . publicAccess = setAclModel . publicAccess ; doc . containerAcl = setAclModel . containerAcl ; doc . properties . lastModified = setAclModel . lastModified ; doc . properties . etag = setAclModel . etag ; return coll . update ( doc ) ; } param { string } account param { string } container param { Models . ContainerAcquireLeaseOptionalParams } options param { Context } context returns { Promise < AcquireContainerLeaseResponse > } memberof public async acquireContainerLease ( context , account , container , options ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = await this . getContainer ( account , container , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } LeaseFactory . createLeaseState ( new ContainerLeaseAdapter ( doc ) , context ) . acquire ( options . duration ! , options . proposedLeaseId ) . sync ( new ContainerLeaseSyncer ( doc ) ) ; coll . update ( doc ) ; return { properties : doc . properties , leaseId : doc . leaseId } ; } param { Context } context param { string } account param { string } container param { string } leaseId param { Models . ContainerReleaseLeaseOptionalParams } options returns { Promise < ReleaseContainerLeaseResponse > } memberof public async releaseContainerLease ( context , account , container , leaseId , options = { } ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = await this . getContainer ( account , container , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } LeaseFactory . createLeaseState ( new ContainerLeaseAdapter ( doc ) , context ) . release ( leaseId ) . sync ( new ContainerLeaseSyncer ( doc ) ) ; coll . update ( doc ) ; return doc . properties ; } param { Context } context param { string } account param { string } container param { string } leaseId param { Models . ContainerRenewLeaseOptionalParams } options returns { Promise < RenewContainerLeaseResponse > } memberof public async renewContainerLease ( context , account , container , leaseId , options = { } ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = await this . getContainer ( account , container , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } LeaseFactory . createLeaseState ( new ContainerLeaseAdapter ( doc ) , context ) . renew ( leaseId ) . sync ( new ContainerLeaseSyncer ( doc ) ) ; coll . update ( doc ) ; return { properties : doc . properties , leaseId : doc . leaseId } ; } param { Context } context param { string } account param { string } container param { ( number | undefined ) } breakPeriod param { Models . ContainerBreakLeaseOptionalParams } options returns { Promise < BreakContainerLeaseResponse > } memberof public async breakContainerLease ( context , account , container , breakPeriod , options = { } ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = await this . getContainer ( account , container , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } LeaseFactory . createLeaseState ( new ContainerLeaseAdapter ( doc ) , context ) . break ( breakPeriod ) . sync ( new ContainerLeaseSyncer ( doc ) ) ; const leaseTimeSeconds = doc . properties . leaseState === Models . LeaseStateType . Breaking && doc . leaseBreakTime ? Math . round ( ( doc . leaseBreakTime . getTime ( ) - context . startTime ! . getTime ( ) ) / 1000 ) : 0 ; coll . update ( doc ) ; return { properties : doc . properties , leaseTime : leaseTimeSeconds } ; } param { Context } context param { string } account param { string } container param { string } leaseId param { string } proposedLeaseId param { Models . ContainerChangeLeaseOptionalParams } options returns { Promise < ChangeContainerLeaseResponse > } memberof public async changeContainerLease ( context , account , container , leaseId , proposedLeaseId , options = { } ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = await this . getContainer ( account , container , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } LeaseFactory . createLeaseState ( new ContainerLeaseAdapter ( doc ) , context ) . change ( leaseId , proposedLeaseId ) . sync ( new ContainerLeaseSyncer ( doc ) ) ; coll . update ( doc ) ; return { properties : doc . properties , leaseId : doc . leaseId } ; } param { string } account param { string } container param { Context } context returns { Promise < void > } memberof public async checkContainerExist ( context , account , container ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = coll . findOne ( { accountName : account , name : container } ) ; if ( ! doc ) { const requestId = context ? context . contextId : undefined ; throw StorageErrorFactory . getContainerNotFound ( requestId ) ; } } public async listBlobs ( context , account , container , blob ? , prefix = "" , maxResults = DEFAULT_LIST_BLOBS_MAX_RESULTS , marker = "" , includeSnapshots ? , includeUncommittedBlobs ? ) { const query = { } ; if ( prefix !== "" ) { query . name = { $regex : `^${ this . escapeRegex ( prefix ) }` } ; } if ( blob !== undefined ) { query . name = blob ; } if ( account !== undefined ) { query . accountName = account ; } if ( container !== undefined ) { query . containerName = container ; } const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const docs = await coll . chain ( ) . find ( query ) . where ( ( obj ) => { return obj . name > marker ! ; } ) . where ( ( obj ) => { return includeSnapshots ? true : obj . snapshot . length === 0 ; } ) . where ( ( obj ) => { return includeUncommittedBlobs ? true : obj . isCommitted ; } ) . sort ( ( obj1 , obj2 ) => { if ( obj1 . name === obj2 . name ) return 0 ; if ( obj1 . name > obj2 . name ) return 1 ; return - 1 ; } ) . limit ( maxResults + 1 ) . data ( ) ; for ( const doc of docs ) { const blobDoc = doc as BlobModel ; blobDoc . properties . contentMD5 = this . restoreUint8Array ( blobDoc . properties . contentMD5 ) ; } if ( docs . length <= maxResults ) { return [ docs . map ( ( doc ) => { return LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( doc ) , context ) . sync ( new BlobLeaseSyncer ( doc ) ) ; } ) , undefined ] ; } else { const nextMarker = docs [ docs . length - 2 ] . name ; docs . pop ( ) ; return [ docs . map ( ( doc ) => { return LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( doc ) , context ) . sync ( new BlobLeaseSyncer ( doc ) ) ; } ) , nextMarker ] ; } } public async listAllBlobs ( maxResults = DEFAULT_LIST_BLOBS_MAX_RESULTS , marker = "" , includeSnapshots ? , includeUncommittedBlobs ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const docs = await coll . chain ( ) . where ( ( obj ) => { return obj . name > marker ! ; } ) . where ( ( obj ) => { return includeSnapshots ? true : obj . snapshot . length === 0 ; } ) . where ( ( obj ) => { return includeUncommittedBlobs ? true : obj . isCommitted ; } ) . simplesort ( "name" ) . limit ( maxResults + 1 ) . data ( ) ; for ( const doc of docs ) { const blobDoc = doc as BlobModel ; blobDoc . properties . contentMD5 = this . restoreUint8Array ( blobDoc . properties . contentMD5 ) ; } if ( docs . length <= maxResults ) { return [ docs , undefined ] ; } else { const nextMarker = docs [ docs . length - 2 ] . name ; docs . pop ( ) ; return [ docs , nextMarker ] ; } } param { Context } context param { BlobModel } blob param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < void > } memberof public async createBlob ( context , blob , leaseAccessConditions ? , modifiedAccessConditions ? ) { await this . checkContainerExist ( context , blob . accountName , blob . containerName ) ; const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const blobDoc = coll . findOne ( { accountName : blob . accountName , containerName : blob . containerName , name : blob . name , snapshot : blob . snapshot } ) ; validateWriteConditions ( context , modifiedAccessConditions , blobDoc ) ; if ( modifiedAccessConditions && modifiedAccessConditions . ifNoneMatch === "*" && blobDoc ) { throw StorageErrorFactory . getBlobAlreadyExists ( context . contextId ) ; } if ( blobDoc ) { LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( blobDoc ) , context ) . validate ( new BlobWriteLeaseValidator ( leaseAccessConditions ) ) . sync ( new BlobLeaseSyncer ( blob ) ) ; if ( blobDoc . properties !== undefined && blobDoc . properties . accessTier === Models . AccessTier . Archive ) { throw StorageErrorFactory . getBlobArchived ( context . contextId ) ; } coll . remove ( blobDoc ) ; } delete ( blob as any ) . $loki ; return coll . insert ( blob ) ; } param { Context } context param { string } account param { string } container param { string } blob param { Models . LeaseAccessConditions } leaseAccessConditions returns { Promise < CreateSnapshotResponse > } memberof public async createSnapshot ( context , account , container , blob , leaseAccessConditions ? , metadata ? , modifiedAccessConditions ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false , true ) ; validateReadConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } new BlobReadLeaseValidator ( leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( doc ) , context ) ; const snapshotTime = convertDateTimeStringMsTo7Digital ( context . startTime ! . toISOString ( ) ) ; const snapshotBlob = { name : doc . name , deleted : false , snapshot : snapshotTime , properties : { ... doc . properties } , metadata : metadata ? { ... metadata } : { ... doc . metadata } , accountName : doc . accountName , containerName : doc . containerName , pageRangesInOrder : doc . pageRangesInOrder === undefined ? undefined : doc . pageRangesInOrder . slice ( ) , isCommitted : doc . isCommitted , committedBlocksInOrder : doc . committedBlocksInOrder === undefined ? undefined : doc . committedBlocksInOrder . slice ( ) , persistency : doc . persistency === undefined ? undefined : { ... doc . persistency } } ; new BlobLeaseSyncer ( snapshotBlob ) . sync ( { leaseId : undefined , leaseExpireTime : undefined , leaseDurationSeconds : undefined , leaseBreakTime : undefined , leaseDurationType : undefined , leaseState : undefined , leaseStatus : undefined } ) ; coll . insert ( snapshotBlob ) ; return { properties : snapshotBlob . properties , snapshot : snapshotTime } ; } param { Context } context param { string } account param { string } container param { string } blob param { string } snapshot param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < BlobModel > } memberof public async downloadBlob ( context , account , container , blob , snapshot = "" , leaseAccessConditions ? , modifiedAccessConditions ? ) { const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , snapshot , context , false , true ) ; validateReadConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } new BlobReadLeaseValidator ( leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( doc ) , context ) ; return doc ; } param { string } account param { string } container param { string } blob param { string } snapshot returns { ( Promise < BlobModel | undefined > ) } memberof public async getBlob ( context , account , container , blob , snapshot = "" ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const blobDoc = coll . findOne ( { accountName : account , containerName : container , name : blob , snapshot } ) ; if ( blobDoc ) { const blobModel = blobDoc as BlobModel ; blobModel . properties . contentMD5 = this . restoreUint8Array ( blobModel . properties . contentMD5 ) ; return blobDoc ; } else { return undefined ; } } param { Context } context param { string } account param { string } container param { string } blob param { string } snapshot param { ( Models . LeaseAccessConditions | undefined ) } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < GetBlobPropertiesRes > } memberof public async getBlobProperties ( context , account , container , blob , snapshot = "" , leaseAccessConditions , modifiedAccessConditions ? ) { const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , snapshot , context , false , true ) ; validateReadConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } new BlobReadLeaseValidator ( leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( doc ) , context ) ; return { properties : doc . properties , metadata : doc . metadata , blobCommittedBlockCount : doc . properties . blobType === Models . BlobType . AppendBlob ? ( doc . committedBlocksInOrder || [ ] ) . length : undefined } ; } param { Context } context param { string } account param { string } container param { string } blob param { Models . BlobDeleteMethodOptionalParams } options returns { Promise < void > } memberof public async deleteBlob ( context , account , container , blob , options ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; await this . checkContainerExist ( context , account , container ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , options . snapshot , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } const againstBaseBlob = doc . snapshot === "" ; if ( ! againstBaseBlob && options . deleteSnapshots !== undefined ) { throw StorageErrorFactory . getInvalidOperation ( context . contextId ! , "Invalid operation against a blob snapshot." ) ; } new BlobWriteLeaseValidator ( options . leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( doc ) , context ) ; if ( againstBaseBlob && options . deleteSnapshots === undefined ) { const count = coll . count ( { accountName : account , containerName : container , name : blob } ) ; if ( count > 1 ) { throw StorageErrorFactory . getSnapshotsPresent ( context . contextId ! ) ; } else { coll . findAndRemove ( { accountName : account , containerName : container , name : blob } ) ; } } if ( ! againstBaseBlob ) { coll . findAndRemove ( { accountName : account , containerName : container , name : blob , snapshot : doc . snapshot } ) ; } if ( againstBaseBlob && options . deleteSnapshots === Models . DeleteSnapshotsOptionType . Include ) { coll . findAndRemove ( { accountName : account , containerName : container , name : blob } ) ; } if ( againstBaseBlob && options . deleteSnapshots === Models . DeleteSnapshotsOptionType . Only ) { const query = { accountName : account , containerName : container , name : blob , snapshot : { $gt : "" } } ; coll . findAndRemove ( query ) ; } } param { Context } context param { string } account param { string } container param { string } blob param { ( Models . LeaseAccessConditions | undefined ) } leaseAccessConditions param { ( Models . BlobHTTPHeaders | undefined ) } blobHTTPHeaders param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < Models . BlobProperties > } memberof public async setBlobHTTPHeaders ( context , account , container , blob , leaseAccessConditions , blobHTTPHeaders , modifiedAccessConditions ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false , true ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } const lease = new BlobLeaseAdapter ( doc ) ; new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( lease , context ) ; const blobHeaders = blobHTTPHeaders ; const blobProps = doc . properties ; if ( blobHeaders !== undefined ) { blobProps . cacheControl = blobHeaders . blobCacheControl ; blobProps . contentType = blobHeaders . blobContentType ; blobProps . contentMD5 = blobHeaders . blobContentMD5 ; blobProps . contentEncoding = blobHeaders . blobContentEncoding ; blobProps . contentLanguage = blobHeaders . blobContentLanguage ; blobProps . contentDisposition = blobHeaders . blobContentDisposition ; } doc . properties = blobProps ; doc . properties . etag = newEtag ( ) ; blobProps . lastModified = context . startTime ? context . startTime : new Date ( ) ; new BlobWriteLeaseSyncer ( doc ) . sync ( lease ) ; coll . update ( doc ) ; return doc . properties ; } param { Context } context param { string } account param { string } container param { string } blob param { ( Models . LeaseAccessConditions | undefined ) } leaseAccessConditions param { ( Models . BlobMetadata | undefined ) } metadata param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < Models . BlobProperties > } memberof public async setBlobMetadata ( context , account , container , blob , leaseAccessConditions , metadata , modifiedAccessConditions ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false , true ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } const lease = new BlobLeaseAdapter ( doc ) ; new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( lease , context ) ; new BlobWriteLeaseSyncer ( doc ) . sync ( lease ) ; doc . metadata = metadata ; doc . properties . etag = newEtag ( ) ; doc . properties . lastModified = context . startTime || new Date ( ) ; coll . update ( doc ) ; return doc . properties ; } param { Context } context param { string } account param { string } container param { string } blob param { number } duration param { string } proposedLeaseId param { Models . BlobAcquireLeaseOptionalParams } options returns { Promise < AcquireBlobLeaseResponse > } memberof public async acquireBlobLease ( context , account , container , blob , duration , proposedLeaseId ? , options = { } ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . snapshot !== "" ) { throw StorageErrorFactory . getBlobSnapshotsPresent ( context . contextId ) ; } LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( doc ) , context ) . acquire ( duration , proposedLeaseId ) . sync ( new BlobLeaseSyncer ( doc ) ) ; coll . update ( doc ) ; return { properties : doc . properties , leaseId : doc . leaseId } ; } param { Context } context param { string } account param { string } container param { string } blob param { string } leaseId param { Models . BlobReleaseLeaseOptionalParams } options returns { Promise < ReleaseBlobLeaseResponse > } memberof public async releaseBlobLease ( context , account , container , blob , leaseId , options = { } ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . snapshot !== "" ) { throw StorageErrorFactory . getBlobSnapshotsPresent ( context . contextId ! ) ; } LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( doc ) , context ) . release ( leaseId ) . sync ( new BlobLeaseSyncer ( doc ) ) ; coll . update ( doc ) ; return doc . properties ; } param { Context } context param { string } account param { string } container param { string } blob param { string } leaseId param { Models . BlobRenewLeaseOptionalParams } options returns { Promise < RenewBlobLeaseResponse > } memberof public async renewBlobLease ( context , account , container , blob , leaseId , options = { } ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . snapshot !== "" ) { throw StorageErrorFactory . getBlobSnapshotsPresent ( context . contextId ! ) ; } LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( doc ) , context ) . renew ( leaseId ) . sync ( new BlobLeaseSyncer ( doc ) ) ; coll . update ( doc ) ; return { properties : doc . properties , leaseId : doc . leaseId } ; } param { Context } context param { string } account param { string } container param { string } blob param { string } leaseId param { string } proposedLeaseId param { Models . BlobChangeLeaseOptionalParams } option returns { Promise < ChangeBlobLeaseResponse > } memberof public async changeBlobLease ( context , account , container , blob , leaseId , proposedLeaseId , options = { } ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . snapshot !== "" ) { throw StorageErrorFactory . getBlobSnapshotsPresent ( context . contextId ! ) ; } LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( doc ) , context ) . change ( leaseId , proposedLeaseId ) . sync ( new BlobLeaseSyncer ( doc ) ) ; coll . update ( doc ) ; return { properties : doc . properties , leaseId : doc . leaseId } ; } param { Context } context param { string } account param { string } container param { string } blob param { ( number | undefined ) } breakPeriod param { Models . BlobBreakLeaseOptionalParams } options returns { Promise < BreakBlobLeaseResponse > } memberof public async breakBlobLease ( context , account , container , blob , breakPeriod , options = { } ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . snapshot !== "" ) { throw StorageErrorFactory . getBlobSnapshotsPresent ( context . contextId ! ) ; } LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( doc ) , context ) . break ( breakPeriod ) . sync ( new BlobLeaseSyncer ( doc ) ) ; const leaseTimeSeconds = doc . properties . leaseState === Models . LeaseStateType . Breaking && doc . leaseBreakTime ? Math . round ( ( doc . leaseBreakTime . getTime ( ) - context . startTime ! . getTime ( ) ) / 1000 ) : 0 ; coll . update ( doc ) ; return { properties : doc . properties , leaseTime : leaseTimeSeconds } ; } param { string } account param { string } container param { string } blob param { string } snapshot param { Context } context returns { Promise < void > } memberof public async checkBlobExist ( context , account , container , blob , snapshot = "" ) { await this . checkContainerExist ( context , account , container ) ; const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = coll . findOne ( { accountName : account , containerName : container , name : blob , snapshot } ) ; if ( ! doc ) { const requestId = context ? context . contextId : undefined ; throw StorageErrorFactory . getBlobNotFound ( requestId ) ; } } param { string } account param { string } container param { string } blob param { string } snapshot returns { ( Promise < * { blobType : Models . BlobType | undefined ; isCommitted : boolean } | undefined * > ) } memberof public async getBlobType ( account , container , blob , snapshot = "" ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = coll . findOne ( { accountName : account , containerName : container , name : blob , snapshot } ) ; if ( ! doc ) { return undefined ; } return { blobType : doc . properties . blobType , isCommitted : doc . isCommitted } ; } param { Context } context param { BlobId } source param { BlobId } destination param { string } copySource param { ( Models . BlobMetadata | undefined ) } metadata param { ( Models . AccessTier | undefined ) } tier param { Models . BlobStartCopyFromURLOptionalParams } leaseAccessConditions returns { Promise < Models . BlobProperties > } memberof public async startCopyFromURL ( context , source , destination , copySource , metadata , tier , options = { } ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const sourceBlob = await this . getBlobWithLeaseUpdated ( source . account , source . container , source . blob , source . snapshot , context , true , true ) ; options . sourceModifiedAccessConditions = options . sourceModifiedAccessConditions || { } ; validateReadConditions ( context , { ifModifiedSince : options . sourceModifiedAccessConditions . sourceIfModifiedSince , ifUnmodifiedSince : options . sourceModifiedAccessConditions . sourceIfUnmodifiedSince , ifMatch : options . sourceModifiedAccessConditions . sourceIfMatches , ifNoneMatch : options . sourceModifiedAccessConditions . sourceIfNoneMatch } , sourceBlob ) ; const destBlob = await this . getBlobWithLeaseUpdated ( destination . account , destination . container , destination . blob , undefined , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , destBlob ) ; if ( destBlob ) { new BlobWriteLeaseValidator ( options . leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( destBlob ) , context ) ; } if ( sourceBlob === undefined || sourceBlob . deleted || ! sourceBlob . isCommitted ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ! ) ; } if ( sourceBlob . properties . accessTier === Models . AccessTier . Archive && ( tier === undefined || source . account !== destination . account ) ) { throw StorageErrorFactory . getBlobArchived ( context . contextId ! ) ; } await this . checkContainerExist ( context , destination . account , destination . container ) ; const copiedBlob = { name : destination . blob , deleted : false , snapshot : "" , properties : { ... sourceBlob . properties , creationTime : context . startTime ! , lastModified : context . startTime ! , etag : newEtag ( ) , leaseStatus : destBlob !== undefined ? destBlob . properties . leaseStatus : Models . LeaseStatusType . Unlocked , leaseState : destBlob !== undefined ? destBlob . properties . leaseState : Models . LeaseStateType . Available , leaseDuration : destBlob !== undefined ? destBlob . properties . leaseDuration : undefined , copyId : uuid ( ) , copyStatus : Models . CopyStatusType . Success , copySource , copyProgress : sourceBlob . properties . contentLength ? `${ sourceBlob . properties . contentLength }/${ sourceBlob . properties . contentLength }` : undefined , copyCompletionTime : context . startTime , copyStatusDescription : undefined , incrementalCopy : false , destinationSnapshot : undefined , deletedTime : undefined , remainingRetentionDays : undefined , archiveStatus : undefined , accessTierChangeTime : undefined } , metadata : metadata === undefined || Object . keys ( metadata ) . length === 0 ? { ... sourceBlob . metadata } : metadata , accountName : destination . account , containerName : destination . container , pageRangesInOrder : sourceBlob . pageRangesInOrder , isCommitted : sourceBlob . isCommitted , leaseDurationSeconds : destBlob !== undefined ? destBlob . leaseDurationSeconds : undefined , leaseId : destBlob !== undefined ? destBlob . leaseId : undefined , leaseExpireTime : destBlob !== undefined ? destBlob . leaseExpireTime : undefined , leaseBreakTime : destBlob !== undefined ? destBlob . leaseBreakTime : undefined , committedBlocksInOrder : sourceBlob . committedBlocksInOrder , persistency : sourceBlob . persistency } ; if ( copiedBlob . properties . blobType === Models . BlobType . BlockBlob && tier !== undefined ) { copiedBlob . properties . accessTier = this . parseTier ( tier ) ; if ( copiedBlob . properties . accessTier === undefined ) { throw StorageErrorFactory . getInvalidHeaderValue ( context . contextId , { HeaderName : "x-ms-access-tier" , HeaderValue : `${ tier }` } ) ; } } if ( copiedBlob . properties . blobType === Models . BlobType . PageBlob && tier !== undefined ) { throw StorageErrorFactory . getInvalidHeaderValue ( context . contextId , { HeaderName : "x-ms-access-tier" , HeaderValue : `${ tier }` } ) ; } if ( destBlob ) { coll . remove ( destBlob ) ; } coll . insert ( copiedBlob ) ; return copiedBlob . properties ; } param { Context } context param { BlobId } source param { BlobId } destination param { string } copySource param { ( Models . BlobMetadata | undefined ) } metadata param { ( Models . AccessTier | undefined ) } tier param { Models . BlobCopyFromURLOptionalParams } leaseAccessConditions returns { Promise < Models . BlobProperties > } memberof public async copyFromURL ( context , source , destination , copySource , metadata , tier , options = { } ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const sourceBlob = await this . getBlobWithLeaseUpdated ( source . account , source . container , source . blob , source . snapshot , context , true , true ) ; options . sourceModifiedAccessConditions = options . sourceModifiedAccessConditions || { } ; validateReadConditions ( context , { ifModifiedSince : options . sourceModifiedAccessConditions . sourceIfModifiedSince , ifUnmodifiedSince : options . sourceModifiedAccessConditions . sourceIfUnmodifiedSince , ifMatch : options . sourceModifiedAccessConditions . sourceIfMatches , ifNoneMatch : options . sourceModifiedAccessConditions . sourceIfNoneMatch } , sourceBlob ) ; const destBlob = await this . getBlobWithLeaseUpdated ( destination . account , destination . container , destination . blob , undefined , context , false ) ; validateWriteConditions ( context , options . modifiedAccessConditions , destBlob ) ; if ( destBlob ) { const lease = new BlobLeaseAdapter ( destBlob ) ; new BlobWriteLeaseSyncer ( destBlob ) . sync ( lease ) ; new BlobWriteLeaseValidator ( options . leaseAccessConditions ) . validate ( lease , context ) ; } if ( sourceBlob === undefined || sourceBlob . deleted || ! sourceBlob . isCommitted ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ! ) ; } if ( sourceBlob . properties . accessTier === Models . AccessTier . Archive ) { throw StorageErrorFactory . getBlobArchived ( context . contextId ! ) ; } await this . checkContainerExist ( context , destination . account , destination . container ) ; const copiedBlob = { name : destination . blob , deleted : false , snapshot : "" , properties : { ... sourceBlob . properties , creationTime : context . startTime ! , lastModified : context . startTime ! , etag : newEtag ( ) , leaseStatus : destBlob !== undefined ? destBlob . properties . leaseStatus : Models . LeaseStatusType . Unlocked , leaseState : destBlob !== undefined ? destBlob . properties . leaseState : Models . LeaseStateType . Available , leaseDuration : destBlob !== undefined ? destBlob . properties . leaseDuration : undefined , copyId : uuid ( ) , copyStatus : Models . CopyStatusType . Success , copySource , copyProgress : sourceBlob . properties . contentLength ? `${ sourceBlob . properties . contentLength }/${ sourceBlob . properties . contentLength }` : undefined , copyCompletionTime : context . startTime , copyStatusDescription : undefined , incrementalCopy : false , destinationSnapshot : undefined , deletedTime : undefined , remainingRetentionDays : undefined , archiveStatus : undefined , accessTierChangeTime : undefined } , metadata : metadata === undefined || Object . keys ( metadata ) . length === 0 ? { ... sourceBlob . metadata } : metadata , accountName : destination . account , containerName : destination . container , pageRangesInOrder : sourceBlob . pageRangesInOrder , isCommitted : sourceBlob . isCommitted , leaseDurationSeconds : destBlob !== undefined ? destBlob . leaseDurationSeconds : undefined , leaseId : destBlob !== undefined ? destBlob . leaseId : undefined , leaseExpireTime : destBlob !== undefined ? destBlob . leaseExpireTime : undefined , leaseBreakTime : destBlob !== undefined ? destBlob . leaseBreakTime : undefined , committedBlocksInOrder : sourceBlob . committedBlocksInOrder , persistency : sourceBlob . persistency } ; if ( copiedBlob . properties . blobType === Models . BlobType . BlockBlob && tier !== undefined ) { copiedBlob . properties . accessTier = this . parseTier ( tier ) ; if ( copiedBlob . properties . accessTier === undefined ) { throw StorageErrorFactory . getInvalidHeaderValue ( context . contextId , { HeaderName : "x-ms-access-tier" , HeaderValue : `${ tier }` } ) ; } } if ( copiedBlob . properties . blobType === Models . BlobType . PageBlob && tier !== undefined ) { throw StorageErrorFactory . getInvalidHeaderValue ( context . contextId , { HeaderName : "x-ms-access-tier" , HeaderValue : `${ tier }` } ) ; } if ( destBlob ) { coll . remove ( destBlob ) ; } coll . insert ( copiedBlob ) ; return copiedBlob . properties ; } param { Context } context param { string } account param { string } container param { string } blob param { Models . AccessTier } tier param { ( Models . LeaseAccessConditions | undefined ) } leaseAccessConditions returns { ( Promise < 200 | 202 > ) } memberof public async setTier ( context , account , container , blob , tier , leaseAccessConditions ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , true , true ) ; let responseCode = 200 ; const lease = new BlobLeaseAdapter ( doc ) ; new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( lease , context ) ; if ( doc . snapshot !== "" ) { throw StorageErrorFactory . getBlobSnapshotsPresent ( context . contextId ! ) ; } if ( ( tier === Models . AccessTier . Archive || tier === Models . AccessTier . Cool || tier === Models . AccessTier . Hot ) && doc . properties . blobType === Models . BlobType . BlockBlob ) { if ( doc . properties . accessTier === Models . AccessTier . Archive && ( tier === Models . AccessTier . Cool || tier === Models . AccessTier . Hot ) ) { responseCode = 202 ; } doc . properties . accessTier = tier ; doc . properties . accessTierInferred = false ; doc . properties . accessTierChangeTime = context . startTime ; } else { throw StorageErrorFactory . getAccessTierNotSupportedForBlobType ( context . contextId ! ) ; } new BlobWriteLeaseSyncer ( doc ) . sync ( lease ) ; coll . update ( doc ) ; return responseCode ; } param { BlockModel } block param { Context } context returns { Promise < void > } memberof public async stageBlock ( context , block , leaseAccessConditions ? ) { await this . checkContainerExist ( context , block . accountName , block . containerName ) ; const blobColl = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const blobDoc = blobColl . findOne ( { accountName : block . accountName , containerName : block . containerName , name : block . blobName } ) ; let blobExist = false ; if ( ! blobDoc ) { const etag = newEtag ( ) ; const newBlob = { deleted : false , accountName : block . accountName , containerName : block . containerName , name : block . blobName , properties : { creationTime : context . startTime , lastModified : context . startTime , etag , contentLength : 0 , blobType : Models . BlobType . BlockBlob } , snapshot : "" , isCommitted : false } ; blobColl . insert ( newBlob ) ; } else { if ( blobDoc . properties . blobType !== Models . BlobType . BlockBlob ) { throw StorageErrorFactory . getBlobInvalidBlobType ( context . contextId ) ; } LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( blobDoc ) , context ) . validate ( new BlobWriteLeaseValidator ( leaseAccessConditions ) ) . sync ( new BlobWriteLeaseSyncer ( blobDoc ) ) ; blobExist = true ; } const coll = this . db . getCollection ( this . BLOCKS_COLLECTION ) ; if ( blobExist ) { const existBlockDoc = coll . findOne ( { accountName : block . accountName , containerName : block . containerName , blobName : block . blobName } ) ; if ( existBlockDoc ) { if ( Buffer . from ( existBlockDoc . name , "base64" ) . length !== Buffer . from ( block . name , "base64" ) . length ) { throw StorageErrorFactory . getInvalidBlobOrBlock ( context . contextId ) ; } } } const blockDoc = coll . findOne ( { accountName : block . accountName , containerName : block . containerName , blobName : block . blobName , name : block . name , isCommitted : block . isCommitted } ) ; if ( blockDoc ) { coll . remove ( blockDoc ) ; } delete ( block as any ) . $loki ; coll . insert ( block ) ; } public async appendBlock ( context , block , leaseAccessConditions = { } , modifiedAccessConditions = { } , appendPositionAccessConditions = { } ) { const doc = await this . getBlobWithLeaseUpdated ( block . accountName , block . containerName , block . blobName , undefined , context , false , true ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( doc ) , context ) ; if ( doc . properties . blobType !== Models . BlobType . AppendBlob ) { throw StorageErrorFactory . getBlobInvalidBlobType ( context . contextId ) ; } if ( ( doc . committedBlocksInOrder || [ ] ) . length >= MAX_APPEND_BLOB_BLOCK_COUNT ) { throw StorageErrorFactory . getBlockCountExceedsLimit ( context . contextId ) ; } if ( appendPositionAccessConditions . appendPosition !== undefined ) { if ( ( doc . properties . contentLength || 0 ) !== appendPositionAccessConditions . appendPosition ) { throw StorageErrorFactory . getAppendPositionConditionNotMet ( context . contextId ) ; } } if ( appendPositionAccessConditions . maxSize !== undefined ) { if ( ( doc . properties . contentLength || 0 ) + block . size > appendPositionAccessConditions . maxSize ) { throw StorageErrorFactory . getMaxBlobSizeConditionNotMet ( context . contextId ) ; } } const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; doc . committedBlocksInOrder = doc . committedBlocksInOrder || [ ] ; doc . committedBlocksInOrder . push ( block ) ; doc . properties . etag = newEtag ( ) ; doc . properties . lastModified = context . startTime || new Date ( ) ; doc . properties . contentLength = ( doc . properties . contentLength || 0 ) + block . size ; coll . update ( doc ) ; return doc . properties ; } param { Context } context param { BlobModel } blob param { { blockName : string ; blockCommitType : string } [ ] } blockList param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < void > } memberof public async commitBlockList ( context , blob , blockList , leaseAccessConditions ? , modifiedAccessConditions ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( blob . accountName , blob . containerName , blob . name , blob . snapshot , context , false ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; if ( modifiedAccessConditions && modifiedAccessConditions . ifNoneMatch === "*" && doc && doc . isCommitted ) { throw StorageErrorFactory . getBlobAlreadyExists ( context . contextId ) ; } let lease ; if ( doc ) { if ( doc . properties . blobType !== Models . BlobType . BlockBlob ) { throw StorageErrorFactory . getBlobInvalidBlobType ( context . contextId ) ; } lease = new BlobLeaseAdapter ( doc ) ; new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( lease , context ) ; } const blockColl = this . db . getCollection ( this . BLOCKS_COLLECTION ) ; const pUncommittedBlocks = blockColl . chain ( ) . find ( { accountName : blob . accountName , containerName : blob . containerName , blobName : blob . name } ) . data ( ) ; const pCommittedBlocksMap = new Map ( ) ; if ( doc ) { for ( const pBlock of doc . committedBlocksInOrder || [ ] ) { pCommittedBlocksMap . set ( pBlock . name , pBlock ) ; } } const pUncommittedBlocksMap = new Map ( ) ; for ( const pBlock of pUncommittedBlocks ) { if ( ! pBlock . isCommitted ) { pUncommittedBlocksMap . set ( pBlock . name , pBlock ) ; } } const selectedBlockList = [ ] ; for ( const block_1 of blockList ) { switch ( block_1 . blockCommitType . toLowerCase ( ) ) { case "uncommitted" : const pUncommittedBlock = pUncommittedBlocksMap . get ( block_1 . blockName ) ; if ( pUncommittedBlock === undefined ) { throw StorageErrorFactory . getInvalidBlockList ( context . contextId ! ) ; } else { selectedBlockList . push ( pUncommittedBlock ) ; } break ; case "committed" : const pCommittedBlock = pCommittedBlocksMap . get ( block_1 . blockName ) ; if ( pCommittedBlock === undefined ) { throw StorageErrorFactory . getInvalidBlockList ( context . contextId ! ) ; } else { selectedBlockList . push ( pCommittedBlock ) ; } break ; case "latest" : const pLatestBlock = pUncommittedBlocksMap . get ( block_1 . blockName ) || pCommittedBlocksMap . get ( block_1 . blockName ) ; if ( pLatestBlock === undefined ) { throw StorageErrorFactory . getInvalidBlockList ( context . contextId ! ) ; } else { selectedBlockList . push ( pLatestBlock ) ; } break ; default : throw StorageErrorFactory . getInvalidBlockList ( context . contextId ! ) ; } } if ( doc ) { doc . properties . blobType = blob . properties . blobType ; doc . properties . lastModified = blob . properties . lastModified ; doc . committedBlocksInOrder = selectedBlockList ; doc . isCommitted = true ; doc . metadata = blob . metadata ; doc . properties . accessTier = blob . properties . accessTier ; doc . properties . accessTierInferred = blob . properties . accessTierInferred ; doc . properties . etag = blob . properties . etag ; doc . properties . cacheControl = blob . properties . cacheControl ; doc . properties . contentType = blob . properties . contentType ; doc . properties . contentMD5 = blob . properties . contentMD5 ; doc . properties . contentEncoding = blob . properties . contentEncoding ; doc . properties . contentLanguage = blob . properties . contentLanguage ; doc . properties . contentDisposition = blob . properties . contentDisposition ; doc . properties . contentLength = selectedBlockList . map ( ( block ) => block . size ) . reduce ( ( total , val ) => { return total + val ; } , 0 ) ; if ( lease ) { new BlobWriteLeaseSyncer ( doc ) . sync ( lease ) ; } coll . update ( doc ) ; } else { blob . committedBlocksInOrder = selectedBlockList ; blob . properties . contentLength = selectedBlockList . map ( ( block ) => block . size ) . reduce ( ( total , val ) => { return total + val ; } , 0 ) ; coll . insert ( blob ) ; } blockColl . findAndRemove ( { accountName : blob . accountName , containerName : blob . containerName , blobName : blob . name } ) ; } param { string } account param { string } container param { string } blob param { ( string | undefined ) } snapshot param { ( boolean | undefined ) } isCommitted param { Context } context returns { Promise < { properties : Models . BlobProperties ; uncommittedBlocks : Models . Block [ ] ; committedBlocks : Models . Block [ ] ; * } > } memberof public async getBlockList ( context , account , container , blob , snapshot , isCommitted , leaseAccessConditions ) { const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , snapshot , context ) ; new BlobReadLeaseValidator ( leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( doc ) , context ) ; if ( doc . properties . blobType !== Models . BlobType . BlockBlob ) { throw StorageErrorFactory . getBlobInvalidBlobType ( context . contextId ) ; } const res = { properties : doc . properties , uncommittedBlocks : [ ] , committedBlocks : [ ] } ; if ( isCommitted !== false && doc . committedBlocksInOrder !== undefined ) { res . committedBlocks = doc . committedBlocksInOrder ; } if ( isCommitted !== true ) { const blockColl = this . db . getCollection ( this . BLOCKS_COLLECTION ) ; const blocks = await blockColl . chain ( ) . find ( { accountName : account , containerName : container , blobName : blob } ) . simplesort ( "$loki" ) . data ( ) ; for ( const item of blocks ) { res . uncommittedBlocks . push ( item ) ; } } return res ; } param { Context } context param { BlobModel } blob param { number } start param { number } end param { IExtentChunk } persistency param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions param { Models . SequenceNumberAccessConditions } sequenceNumberAccessConditions returns { Promise < Models . BlobProperties > } memberof public async uploadPages ( context , blob , start , end , persistency , leaseAccessConditions ? , modifiedAccessConditions ? , sequenceNumberAccessConditions ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( blob . accountName , blob . containerName , blob . name , blob . snapshot , context ! , false , true ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; validateSequenceNumberWriteConditions ( context , sequenceNumberAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . properties . blobType !== Models . BlobType . PageBlob ) { throw StorageErrorFactory . getBlobInvalidBlobType ( context . contextId ) ; } const lease = new BlobLeaseAdapter ( doc ) ; new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( lease , context ) ; this . pageBlobRangesManager . mergeRange ( doc . pageRangesInOrder || [ ] , { start , end , persistency } ) ; new BlobWriteLeaseSyncer ( doc ) . sync ( lease ) ; doc . properties . etag = newEtag ( ) ; doc . properties . lastModified = context . startTime || new Date ( ) ; coll . update ( doc ) ; return doc . properties ; } param { Context } context param { BlobModel } blob param { number } start param { number } end param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions param { Models . SequenceNumberAccessConditions } sequenceNumberAccessConditions returns { Promise < Models . BlobProperties > } memberof public async clearRange ( context , blob , start , end , leaseAccessConditions ? , modifiedAccessConditions ? , sequenceNumberAccessConditions ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( blob . accountName , blob . containerName , blob . name , blob . snapshot , context ! , false , true ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; validateSequenceNumberWriteConditions ( context , sequenceNumberAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } const lease = new BlobLeaseAdapter ( doc ) ; new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( lease , context ) ; this . pageBlobRangesManager . clearRange ( doc . pageRangesInOrder || [ ] , { start , end } ) ; new BlobWriteLeaseSyncer ( doc ) . sync ( lease ) ; doc . properties . etag = newEtag ( ) ; doc . properties . lastModified = context . startTime || new Date ( ) ; coll . update ( doc ) ; return doc . properties ; } param { Context } context param { string } account param { string } container param { string } blob param { string } snapshot param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < GetPageRangeResponse > } memberof public async getPageRanges ( context , account , container , blob , snapshot ? , leaseAccessConditions ? , modifiedAccessConditions ? ) { const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , snapshot , context , false , true ) ; validateReadConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . properties . blobType !== Models . BlobType . PageBlob ) { throw StorageErrorFactory . getBlobInvalidBlobType ( context . contextId ) ; } new BlobReadLeaseValidator ( leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( doc ) , context ) ; return { properties : doc . properties , pageRangesInOrder : doc . pageRangesInOrder } ; } param { Context } context param { string } account param { string } container param { string } blob param { number } blobContentLength param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < Models . BlobProperties > } memberof public async resizePageBlob ( context , account , container , blob , blobContentLength , leaseAccessConditions ? , modifiedAccessConditions ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false , true ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . properties . blobType !== Models . BlobType . PageBlob ) { throw StorageErrorFactory . getInvalidOperation ( context . contextId , "Resize could only be against a page blob." ) ; } const lease = new BlobLeaseAdapter ( doc ) ; new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( lease , context ) ; doc . pageRangesInOrder = doc . pageRangesInOrder || [ ] ; if ( doc . properties . contentLength ! > blobContentLength ) { const start = blobContentLength ; const end = doc . properties . contentLength ! - 1 ; this . pageBlobRangesManager . clearRange ( doc . pageRangesInOrder || [ ] , { start , end } ) ; } doc . properties . contentLength = blobContentLength ; doc . properties . lastModified = context . startTime || new Date ( ) ; doc . properties . etag = newEtag ( ) ; new BlobWriteLeaseSyncer ( doc ) . sync ( lease ) ; coll . update ( doc ) ; return doc . properties ; } param { Context } context param { string } account param { string } container param { string } blob param { Models . SequenceNumberActionType } sequenceNumberAction param { ( number | undefined ) } blobSequenceNumber param { Models . LeaseAccessConditions } leaseAccessConditions param { Models . ModifiedAccessConditions } modifiedAccessConditions returns { Promise < Models . BlobProperties > } memberof public async updateSequenceNumber ( context , account , container , blob , sequenceNumberAction , blobSequenceNumber , leaseAccessConditions ? , modifiedAccessConditions ? ) { const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = await this . getBlobWithLeaseUpdated ( account , container , blob , undefined , context , false , true ) ; validateWriteConditions ( context , modifiedAccessConditions , doc ) ; if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } if ( doc . properties . blobType !== Models . BlobType . PageBlob ) { throw StorageErrorFactory . getInvalidOperation ( context . contextId ! , "Get Page Ranges could only be against a page blob." ) ; } const lease = new BlobLeaseAdapter ( doc ) ; new BlobWriteLeaseValidator ( leaseAccessConditions ) . validate ( lease , context ) ; if ( doc . properties . blobSequenceNumber === undefined ) { doc . properties . blobSequenceNumber = 0 ; } switch ( sequenceNumberAction ) { case Models . SequenceNumberActionType . Max : if ( blobSequenceNumber === undefined ) { throw StorageErrorFactory . getInvalidOperation ( context . contextId ! , "x-ms-blob-sequence-number is required when x-ms-sequence-number-action is set to max." ) ; } doc . properties . blobSequenceNumber = Math . max ( doc . properties . blobSequenceNumber , blobSequenceNumber ) ; break ; case Models . SequenceNumberActionType . Increment : if ( blobSequenceNumber !== undefined ) { throw StorageErrorFactory . getInvalidOperation ( context . contextId ! , "x-ms-blob-sequence-number cannot be provided when x-ms-sequence-number-action is set to increment." ) ; } doc . properties . blobSequenceNumber ++ ; break ; case Models . SequenceNumberActionType . Update : if ( blobSequenceNumber === undefined ) { throw StorageErrorFactory . getInvalidOperation ( context . contextId ! , "x-ms-blob-sequence-number is required when x-ms-sequence-number-action is set to update." ) ; } doc . properties . blobSequenceNumber = blobSequenceNumber ; break ; default : throw StorageErrorFactory . getInvalidOperation ( context . contextId ! , "Unsupported x-ms-sequence-number-action value." ) ; } doc . properties . etag = newEtag ( ) ; doc . properties . lastModified = context . startTime ! ; new BlobWriteLeaseSyncer ( doc ) . sync ( lease ) ; coll . update ( doc ) ; return doc . properties ; } public async listUncommittedBlockPersistencyChunks ( marker = "-1" , maxResults = 2000 ) { const coll = this . db . getCollection ( this . BLOCKS_COLLECTION ) ; const blockDocs = coll . chain ( ) . where ( ( obj ) => { return obj . $loki > parseInt ( marker , 10 ) ; } ) . simplesort ( "$loki" ) . limit ( maxResults + 1 ) . data ( ) ; if ( blockDocs . length <= maxResults ) { return [ blockDocs . map ( ( block ) => block . persistency ) , undefined ] ; } else { blockDocs . pop ( ) ; const nextMarker = `${ blockDocs [ maxResults - 1 ] . $loki }` ; return [ blockDocs . map ( ( block ) => block . persistency ) , nextMarker ] ; } } private param { * } obj returns { ( Uint8Array | undefined ) } memberof private restoreUint8Array ( obj ) { if ( typeof obj !== "object" ) { return undefined ; } if ( obj instanceof Uint8Array ) { return obj ; } if ( obj . type === "Buffer" ) { obj = obj . data ; } const length = Object . keys ( obj ) . length ; const arr = Buffer . allocUnsafe ( length ) ; for ( let i = 0 ; i < length ; i ++ ) { if ( ! obj . hasOwnProperty ( i ) ) { throw new TypeError ( `Cannot restore loki DB persisted object to Uint8Array. Key ${ i } is missing.` ) ; } arr [ i ] = obj [ i ] ; } return arr ; } private param { string } regex returns { string } memberof private escapeRegex ( regex ) { return regex . replace ( /[-\/\\^$*+?.()|[\]{}]/g , "\\$&" ) ; } private param { string } account param { string } container param { Context } context returns { Promise < ContainerModel > } memberof private param { string } account param { string } container param { Context } context returns { Promise < ContainerModel > } memberof private async getContainerWithLeaseUpdated ( account , container , context , forceExist ? ) ; private param { string } account param { string } container param { Context } context param { false } forceExist returns { ( Promise < ContainerModel | undefined > ) } memberof private async getContainerWithLeaseUpdated ( account , container , context , forceExist ) ; private async getContainerWithLeaseUpdated ( account , container , context , forceExist ? ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = coll . findOne ( { accountName : account , name : container } ) ; if ( forceExist === undefined || forceExist === true ) { if ( ! doc ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } } if ( ! doc ) { return undefined ; } LeaseFactory . createLeaseState ( new ContainerLeaseAdapter ( doc ) , context ) . sync ( new ContainerLeaseSyncer ( doc ) ) ; return doc ; } private param { string } account param { string } container param { Context } context param { true } forceExist returns { Promise < ContainerModel > } memberof private async getContainer ( account , container , context , forceExist ? ) ; private param { string } account param { string } container param { Context } context param { false } forceExist returns { Promise < ContainerModel > } memberof private async getContainer ( account , container , context , forceExist ) ; private async getContainer ( account , container , context , forceExist ? ) { const coll = this . db . getCollection ( this . CONTAINERS_COLLECTION ) ; const doc = coll . findOne ( { accountName : account , name : container } ) ; if ( ! doc ) { if ( forceExist ) { throw StorageErrorFactory . getContainerNotFound ( context . contextId ) ; } else { return undefined ; } } return doc ; } private param { string } account param { string } container param { string } blob param { ( string | undefined ) } snapshot param { Context } context param { undefined } forceExist param { boolean } forceCommitted returns { Promise < BlobModel > } memberof private async getBlobWithLeaseUpdated ( account , container , blob , snapshot , context , forceExist ? , forceCommitted ? ) ; private param { string } account param { string } container param { string } blob param { ( string | undefined ) } snapshot param { Context } context param { false } forceExist param { boolean } forceCommitted returns { ( Promise < BlobModel | undefined > ) } memberof private async getBlobWithLeaseUpdated ( account , container , blob , snapshot , context , forceExist , forceCommitted ? ) ; private async getBlobWithLeaseUpdated ( account , container , blob , snapshot = "" , context , forceExist ? , forceCommitted ? ) { await this . checkContainerExist ( context , account , container ) ; const coll = this . db . getCollection ( this . BLOBS_COLLECTION ) ; const doc = coll . findOne ( { accountName : account , containerName : container , name : blob , snapshot } ) ; if ( forceExist === undefined || forceExist === true ) { if ( forceCommitted ) { if ( ! doc || ! ( doc as BlobModel ) . isCommitted ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } } else { if ( ! doc ) { throw StorageErrorFactory . getBlobNotFound ( context . contextId ) ; } } } else { if ( forceCommitted ) { if ( ! doc || ! ( doc as BlobModel ) . isCommitted ) { return undefined ; } } else { if ( ! doc ) { return undefined ; } } } if ( doc . properties ) { doc . properties . contentMD5 = this . restoreUint8Array ( doc . properties . contentMD5 ) ; } if ( snapshot !== undefined && snapshot !== "" ) { new BlobLeaseSyncer ( doc ) . sync ( { leaseId : undefined , leaseExpireTime : undefined , leaseDurationSeconds : undefined , leaseBreakTime : undefined , leaseDurationType : undefined , leaseState : Models . LeaseStateType . Available , leaseStatus : Models . LeaseStatusType . Unlocked } ) ; } else { LeaseFactory . createLeaseState ( new BlobLeaseAdapter ( doc ) , context ) . sync ( new BlobLeaseSyncer ( doc ) ) ; } return doc ; } private param { string } tier returns { ( Models . AccessTier | undefined ) } memberof private parseTier ( tier ) { tier = tier . toLowerCase ( ) ; if ( tier === Models . AccessTier . Hot . toLowerCase ( ) ) { return Models . AccessTier . Hot ; } if ( tier === Models . AccessTier . Cool . toLowerCase ( ) ) { return Models . AccessTier . Cool ; } if ( tier === Models . AccessTier . Archive . toLowerCase ( ) ) { return Models . AccessTier . Archive ; } return undefined ; } }