import { PageMeta } from './PageMeta' ; import { DocMeta } from './DocMeta' ; import { PagemarkType } from 'polar-shared/src/metadata/PagemarkType' ; import { PageInfo } from './PageInfo' ; import { DocInfos } from './DocInfos' ; import { AnnotationInfos } from './AnnotationInfos' ; import { Pagemarks } from './Pagemarks' ; import { PageMetas } from './PageMetas' ; import { forDict } from 'polar-shared/src/util/Functions' ; import { TextHighlights } from './TextHighlights' ; import { Preconditions } from 'polar-shared/src/Preconditions' ; import { Errors } from '../util/Errors' ; import { ISODateTimeStrings } from 'polar-shared/src/metadata/ISODateTimeStrings' ; import { FilePaths } from 'polar-shared/src/util/FilePaths' ; import { Datastore } from '../datastore/Datastore' ; import { Backend } from 'polar-shared/src/datastore/Backend' ; import { IPageMeta } from "polar-shared/src/metadata/IPageMeta" ; import { IDocMeta } from "polar-shared/src/metadata/IDocMeta" ; import { FileRef } from "polar-shared/src/datastore/FileRef" ; import { ITextHighlight } from "polar-shared/src/metadata/ITextHighlight" ; import { IAreaHighlight } from "polar-shared/src/metadata/IAreaHighlight" ; import { IFlashcard } from "polar-shared/src/metadata/IFlashcard" ; import { IComment } from 'polar-shared/src/metadata/IComment' ; import { AnnotationType } from 'polar-shared/src/metadata/AnnotationType' ; import { Dictionaries } from "polar-shared/src/util/Dictionaries" ; import { UUIDs } from "./UUIDs" ; import { SparseDocMetas } from "./SparseDocMetas" ; export type AnnotationCallback = ( pageMeta : IPageMeta , annotation : ITextHighlight | IAreaHighlight | IFlashcard | IComment , type : AnnotationType ) => void ; export class DocMetas { public static ENABLE_SPARSE_DOC_SERIALIZE = true ; public static annotations ( docMeta , callback ) { for ( const pageMeta of Object . values ( docMeta . pageMetas ) ) { for ( const annotation of Object . values ( pageMeta . textHighlights || { } ) ) { callback ( pageMeta , annotation , AnnotationType . TEXT_HIGHLIGHT ) ; } for ( const annotation of Object . values ( pageMeta . areaHighlights || { } ) ) { callback ( pageMeta , annotation , AnnotationType . AREA_HIGHLIGHT ) ; } for ( const annotation of Object . values ( pageMeta . flashcards || { } ) ) { callback ( pageMeta , annotation , AnnotationType . FLASHCARD ) ; } for ( const annotation of Object . values ( pageMeta . comments || { } ) ) { callback ( pageMeta , annotation , AnnotationType . COMMENT ) ; } } } param fingerprint param nrPages public static create ( fingerprint , nrPages , filename ? ) { const docInfo = DocInfos . create ( fingerprint , nrPages , filename ) ; const pageMetas = { } ; for ( let idx = 1 ; idx <= nrPages ; ++ idx ) { const pageInfo = new PageInfo ( { num : idx } ) ; const pageMeta = new PageMeta ( { pageInfo } ) ; pageMetas [ idx ] = pageMeta ; } return new DocMeta ( docInfo , pageMetas ) ; } deprecated public static createWithinInitialPagemarks ( fingerprint , nrPages ) { return MockDocMetas . createWithinInitialPagemarks ( fingerprint , nrPages ) ; } deprecated public static createMockDocMeta ( ) { return MockDocMetas . createMockDocMeta ( ) ; } public static getPageMeta ( docMeta , num ) { Preconditions . assertPresent ( docMeta , "docMeta" ) ; Preconditions . assertPresent ( num , "num" ) ; const pageMeta = docMeta . pageMetas [ num ] ; if ( ! pageMeta ) { throw new Error ( "No pageMeta for page: " + num ) ; } return pageMeta ; } public static addPagemarks ( docMeta , options ) { if ( ! options ) { options = { } ; } if ( ! options . nrPages ) { options . nrPages = 3 ; } if ( ! options . offsetPage ) { options . offsetPage = 1 ; } if ( ! options . percentage ) { options . percentage = 100 ; } const maxPageNum = Math . min ( options . offsetPage + options . nrPages - 1 , docMeta . docInfo . nrPages ) ; for ( let pageNum = options . offsetPage ; pageNum <= maxPageNum ; ++ pageNum ) { const pagemark = Pagemarks . create ( { type : PagemarkType . SINGLE_COLUMN , percentage : 100 , column : 0 } ) ; Pagemarks . updatePagemark ( docMeta , pageNum , pagemark ) ; } } public static serialize ( docMeta , spacing = "  " ) { if ( this . ENABLE_SPARSE_DOC_SERIALIZE ) { const data = SparseDocMetas . toSparse ( docMeta ) ; return JSON . stringify ( data , null , spacing ) ; } else { return JSON . stringify ( docMeta , null , spacing ) ; } } /**
     */ public static deserialize ( data , fingerprint ) { Preconditions . assertPresent ( data , 'data' ) ; if ( typeof data !== "string" ) { throw new Error ( "We can only deserialize strings: " + typeof data ) ; } const docMeta = Object . create ( DocMeta . prototype ) ; try { let obj = JSON . parse ( data ) ; if ( SparseDocMetas . isSparse ( obj ) ) { obj = SparseDocMetas . fromSparse ( obj ) ; } Object . assign ( docMeta , obj ) ; if ( docMeta . docInfo && ! docMeta . docInfo . filename ) { } return DocMetas . upgrade ( docMeta ) ; } catch ( e ) { throw Errors . rethrow ( e , "Unable to deserialize doc: " + fingerprint ) ; } } public static upgrade ( docMeta ) { docMeta . pageMetas = PageMetas . upgrade ( docMeta . pageMetas ) ; if ( ! docMeta . annotationInfo ) { docMeta . annotationInfo = AnnotationInfos . create ( ) ; } if ( ! docMeta . attachments ) { docMeta . attachments = { } ; } docMeta . docInfo = DocInfos . upgrade ( docMeta . docInfo ) ; return docMeta ; } /**
     * Compute the progress of a document based on the pagemarks.
     */ public static computeProgress ( docMeta ) { let total = 0 ; forDict ( docMeta . pageMetas , ( key , pageMeta ) => { forDict ( pageMeta . pagemarks , ( column , pagemark ) => { total += pagemark . percentage ; } ) ; } ) ; return total / ( docMeta . docInfo . nrPages * 100 ) ; } param docMeta param mutator public static withBatchedMutations < T > ( docMeta , mutator ) { return this . withMutating ( docMeta , 'batch' , mutator ) ; } public static withSkippedMutations < T > ( docMeta , mutator ) { return this . withMutating ( docMeta , 'skip' , mutator ) ; } private static withMutating < T > ( docMeta , value , mutator ) { if ( docMeta . docInfo . mutating === value ) { return mutator ( ) ; } try { docMeta . docInfo . mutating = value ; return mutator ( ) ; } finally { docMeta . docInfo . mutating = undefined ; } } /**
     * Force a write of the DocMeta
     */ public static forceWrite ( docMeta ) { docMeta . docInfo . lastUpdated = ISODateTimeStrings . create ( ) ; } /**
     * Create a copy of the DocMeta and with updated lastUpdate fields and
     * a new UUID.
     */ public static updated ( docMeta ) { docMeta = Dictionaries . copyOf ( docMeta ) ; docMeta . docInfo . lastUpdated = ISODateTimeStrings . create ( ) ; docMeta . docInfo . uuid = UUIDs . create ( ) ; const docInfo = Dictionaries . copyOf ( docMeta . docInfo ) ; return Object . assign ( new DocMeta ( docInfo , { } ) , docMeta ) ; } public static copyOf ( docMeta ) { docMeta = Dictionaries . copyOf ( docMeta ) ; const docInfo = Dictionaries . copyOf ( docMeta . docInfo ) ; return Object . assign ( new DocMeta ( docInfo , { } ) , docMeta ) ; } } export class MockDocMetas { /**
     * Create a DocMeta object but place initial pagemarks on it. This is useful
     * for testing.
     *
     */ public static createWithinInitialPagemarks ( fingerprint , nrPages ) { const result = DocMetas . create ( fingerprint , nrPages ) ; const maxPages = 3 ; for ( let pageNum = 1 ; pageNum <= Math . min ( nrPages , maxPages ) ; ++ pageNum ) { const pagemark = Pagemarks . create ( { type : PagemarkType . SINGLE_COLUMN , percentage : 100 , column : 0 } ) ; Pagemarks . updatePagemark ( result , pageNum , pagemark ) ; } return result ; } public static createMockDocMeta ( fingerprint = "0x001" ) { const nrPages = 4 ; const docMeta = DocMetas . createWithinInitialPagemarks ( fingerprint , nrPages ) ; const textHighlight = TextHighlights . createMockTextHighlight ( ) ; DocMetas . getPageMeta ( docMeta , 1 ) . textHighlights [ textHighlight . id ] = textHighlight ; return docMeta ; } public static async createMockDocMetaFromPDF ( datastore ) { const docMeta = MockDocMetas . createMockDocMeta ( ) ; const pdfPath = FilePaths . join ( __dirname , ".." , ".." , ".." , "docs" , "examples" , "pdf" , "chubby.pdf" ) ; const fileRef = { name : "chubby.pdf" } ; docMeta . docInfo . filename = fileRef . name ; docMeta . docInfo . backend = Backend . STASH ; await datastore . writeFile ( Backend . STASH , fileRef , { path : pdfPath } ) ; await datastore . writeDocMeta ( docMeta ) ; const result = { docMeta , fileRef } ; return result ; } } export interface MockDoc { readonly docMeta : IDocMeta ; readonly fileRef : FileRef ; }