import { assert , bufferToString , fromBase64ToUtf8 , IsoBuffer , Uint8ArrayToString , unreachableCase , } from "@fluidframework/common-utils" ; import { AttachmentTreeEntry , BlobTreeEntry , TreeTreeEntry } from "@fluidframework/protocol-base" ; import { ITree , SummaryType , ISummaryTree , SummaryObject , ISummaryBlob , TreeEntry , ITreeEntry , ISnapshotTree , } from "@fluidframework/protocol-definitions" ; import { ISummaryStats , ISummarizeResult , ISummaryTreeWithStats } from "@fluidframework/runtime-definitions" ; param stats export function mergeStats ( ... stats ) { const results = { treeNodeCount : 0 , blobNodeCount : 0 , handleNodeCount : 0 , totalBlobSize : 0 , unreferencedBlobSize : 0 } ; for ( const stat of stats ) { results . treeNodeCount += stat . treeNodeCount ; results . blobNodeCount += stat . blobNodeCount ; results . handleNodeCount += stat . handleNodeCount ; results . totalBlobSize += stat . totalBlobSize ; results . unreferencedBlobSize += stat . unreferencedBlobSize ; } return results ; } export function utf8ByteLength ( str ) { let s = str . length ; for ( let i = str . length - 1 ; i >= 0 ; i -- ) { const code = str . charCodeAt ( i ) ; if ( code > 0x7f && code <= 0x7ff ) { s ++ ; } else if ( code > 0x7ff && code <= 0xffff ) { s += 2 ; } if ( code >= 0xDC00 && code <= 0xDFFF ) { i -- ; } } return s ; } export function getBlobSize ( content ) { if ( typeof content === "string" ) { return utf8ByteLength ( content ) ; } else { return content . byteLength ; } } function calculateStatsCore ( summaryObject , stats ) { switch ( summaryObject . type ) { case SummaryType . Tree : { stats . treeNodeCount ++ ; for ( const value of Object . values ( summaryObject . tree ) ) { calculateStatsCore ( value , stats ) ; } return ; } case SummaryType . Handle : { stats . handleNodeCount ++ ; return ; } case SummaryType . Blob : { stats . blobNodeCount ++ ; stats . totalBlobSize += getBlobSize ( summaryObject . content ) ; return ; } default : return ; } } export function calculateStats ( summary ) { const stats = mergeStats ( ) ; calculateStatsCore ( summary , stats ) ; return stats ; } export function addBlobToSummary ( summary , key , content ) { const blob = { type : SummaryType . Blob , content } ; summary . summary . tree [ key ] = blob ; summary . stats . blobNodeCount ++ ; summary . stats . totalBlobSize += getBlobSize ( content ) ; } export function addTreeToSummary ( summary , key , summarizeResult ) { summary . summary . tree [ key ] = summarizeResult . summary ; summary . stats = mergeStats ( summary . stats , summarizeResult . stats ) ; } export class SummaryTreeBuilder implements ISummaryTreeWithStats { private attachmentCounter : number = 0 ; public get summary ( ) : ISummaryTree { return { type : SummaryType . Tree , tree : { ... this . summaryTree } } ; } public get stats ( ) : Readonly < ISummaryStats > { return { ... this . summaryStats } ; } constructor ( ) { this . summaryStats = mergeStats ( ) ; this . summaryStats . treeNodeCount ++ ; } private readonly summaryTree : { [ path ] : SummaryObject ; } = { } ; private summaryStats : ISummaryStats ; public addBlob ( key , content ) { addBlobToSummary ( { summary : { type : SummaryType . Tree , tree : this . summaryTree } , stats : this . summaryStats } , key , content ) ; } public addHandle ( key , handleType , handle ) { this . summaryTree [ key ] = { type : SummaryType . Handle , handleType , handle } ; this . summaryStats . handleNodeCount ++ ; } public addWithStats ( key , summarizeResult ) { this . summaryTree [ key ] = summarizeResult . summary ; this . summaryStats = mergeStats ( this . summaryStats , summarizeResult . stats ) ; } public addAttachment ( id ) { this . summaryTree [ this . attachmentCounter ++ ] = { id , type : SummaryType . Attachment } ; } public getSummaryTree ( ) { return { summary : this . summary , stats : this . stats } ; } } param snapshot param fullTree export function convertToSummaryTreeWithStats ( snapshot , fullTree = false ) { const builder = new SummaryTreeBuilder ( ) ; for ( const entry of snapshot . entries ) { switch ( entry . type ) { case TreeEntry . Blob : { const blob = entry . value ; let content ; if ( blob . encoding === "base64" ) { content = IsoBuffer . from ( blob . contents , "base64" ) ; } else { content = blob . contents ; } builder . addBlob ( entry . path , content ) ; break ; } case TreeEntry . Tree : { const subtree = convertToSummaryTree ( entry . value , fullTree ) ; builder . addWithStats ( entry . path , subtree ) ; break ; } case TreeEntry . Attachment : { const id = entry . value . id ; builder . addAttachment ( id ) ; break ; } default : throw new Error ( "Unexpected TreeEntry type" ) ; } } const summaryTree = builder . getSummaryTree ( ) ; summaryTree . summary . unreferenced = snapshot . unreferenced ; return summaryTree ; } param snapshot param fullTree export function convertToSummaryTree ( snapshot , fullTree = false ) { if ( snapshot . id && ! fullTree ) { const stats = mergeStats ( ) ; stats . handleNodeCount ++ ; return { summary : { handle : snapshot . id , handleType : SummaryType . Tree , type : SummaryType . Handle } , stats } ; } else { return convertToSummaryTreeWithStats ( snapshot , fullTree ) ; } } param snapshot export function convertSnapshotTreeToSummaryTree ( snapshot ) { assert ( Object . keys ( snapshot . commits ) . length === 0 , 0x19e ) ; const builder = new SummaryTreeBuilder ( ) ; for ( const [ path , id ] of Object . entries ( snapshot . blobs ) ) { let decoded ; if ( ( snapshot as any ) . blobsContents !== undefined ) { const content = ( snapshot as any ) . blobsContents [ id ] ; if ( content !== undefined ) { decoded = bufferToString ( content , "utf-8" ) ; } } else if ( snapshot . blobs [ id ] !== undefined ) { decoded = fromBase64ToUtf8 ( snapshot . blobs [ id ] ) ; } if ( decoded !== undefined ) { builder . addBlob ( path , decoded ) ; } } for ( const [ key , tree ] of Object . entries ( snapshot . trees ) ) { const subtree = convertSnapshotTreeToSummaryTree ( tree ) ; builder . addWithStats ( key , subtree ) ; } const summaryTree = builder . getSummaryTree ( ) ; summaryTree . summary . unreferenced = snapshot . unreferenced ; return summaryTree ; } param summaryTree export function convertSummaryTreeToITree ( summaryTree ) { const entries = [ ] ; for ( const [ key , value ] of Object . entries ( summaryTree . tree ) ) { switch ( value . type ) { case SummaryType . Blob : { let parsedContent ; let encoding = "utf-8" ; if ( typeof value . content === "string" ) { parsedContent = value . content ; } else { parsedContent = Uint8ArrayToString ( value . content , "base64" ) ; encoding = "base64" ; } entries . push ( new BlobTreeEntry ( key , parsedContent , encoding ) ) ; break ; } case SummaryType . Tree : { entries . push ( new TreeTreeEntry ( key , convertSummaryTreeToITree ( value ) ) ) ; break ; } case SummaryType . Attachment : { entries . push ( new AttachmentTreeEntry ( key , value . id ) ) ; break ; } case SummaryType . Handle : { throw new Error ( "Should not have Handle type in summary tree" ) ; } default : unreachableCase ( value , "Unexpected summary tree type" ) ; } } return { entries , unreferenced : summaryTree . unreferenced } ; }