import { BinaryFileData , Datastore , DatastoreCapabilities , DatastoreInitOpts , DatastoreOverview , DeleteResult , DocMetaSnapshotEventListener , DocMetaSnapshotOpts , DocMetaSnapshotResult , ErrorListener , SnapshotResult , WriteFileOpts } from './Datastore' ; import { DocMeta } from '../metadata/DocMeta' ; import { DocMetas } from '../metadata/DocMetas' ; import { isPresent , Preconditions } from 'polar-shared/src/Preconditions' ; import { Logger } from 'polar-shared/src/logger/Logger' ; import { Dictionaries } from 'polar-shared/src/util/Dictionaries' ; import { DocMetaFileRef , DocMetaRef } from './DocMetaRef' ; import { AbstractPersistenceLayer , PersistenceLayer , WriteOpts } from './PersistenceLayer' ; import { ISODateTimeStrings } from 'polar-shared/src/metadata/ISODateTimeStrings' ; import { Backend } from 'polar-shared/src/datastore/Backend' ; import { DocFileMeta } from 'polar-shared/src/datastore/DocFileMeta' ; import { Reducers } from 'polar-shared/src/util/Reducers' ; import { DatastoreMutation , DefaultDatastoreMutation } from './DatastoreMutation' ; import { DatastoreMutations } from './DatastoreMutations' ; import { UUIDs } from '../metadata/UUIDs' ; import { NULL_FUNCTION } from 'polar-shared/src/util/Functions' ; import { IDocInfo } from "polar-shared/src/metadata/IDocInfo" ; import { IDocMeta } from "polar-shared/src/metadata/IDocMeta" ; import { FileRef } from "polar-shared/src/datastore/FileRef" ; import { DocMetaTags } from "../metadata/DocMetaTags" ; import { UserTagsDB } from "./UserTagsDB" ; import { Latch } from "polar-shared/src/util/Latch" ; import { GetFileOpts } from "polar-shared/src/datastore/IDatastore" ; const log = Logger . create ( ) ; /**
 * First layer before the raw datastore. At one point we allowed the datastore
 * to perform all the data manipulation / serialization but we ran into problems
 * with node+chrome behaving differently so now we just make node work with raw
 * strings.
 */ export class DefaultPersistenceLayer extends AbstractPersistenceLayer implements PersistenceLayer { public readonly id = 'default' ; public readonly datastore : Datastore ; private datastoreMutations : DatastoreMutations ; private userTagsDB ? : UserTagsDB ; private initLatch = new Latch ( ) ; constructor ( datastore ) { super ( ) ; this . datastore = datastore ; this . datastoreMutations = DatastoreMutations . create ( 'written' ) ; } public async init ( errorListener = NULL_FUNCTION , opts ? ) { await this . doInitDatastore ( errorListener , opts ) ; } private async doInitDatastore ( errorListener , opts ) { try { await this . datastore . init ( errorListener , opts ) ; this . initLatch . resolve ( true ) ; } catch ( e ) { this . initLatch . reject ( e ) ; } } public async stop ( ) { await this . datastore . stop ( ) ; } public contains ( fingerprint ) { return this . datastore . contains ( fingerprint ) ; } public delete ( docMetaFileRef , datastoreMutation = new DefaultDatastoreMutation ( ) ) { return this . datastore . delete ( docMetaFileRef , datastoreMutation ) ; } /**
     * Get the DocMeta object we currently in the datastore for this given
     * fingerprint or null if it does not exist.
     */ public async getDocMeta ( fingerprint ) { const data = await this . datastore . getDocMeta ( fingerprint ) ; return this . toDocMeta ( fingerprint , data ) ; } public async getDocMetaSnapshot ( opts ) { return this . datastore . getDocMetaSnapshot ( { ... opts , onSnapshot : ( snapshot => { const data = this . toDocMeta ( opts . fingerprint , snapshot . data ) ; opts . onSnapshot ( { ... snapshot , data } ) ; } ) } ) ; } private toDocMeta ( fingerprint , data ) { if ( ! isPresent ( data ) ) { return undefined ; } if ( ! ( typeof data === "string" ) ) { throw new Error ( "Expected string and received: " + typeof data ) ; } const docMeta = DocMetas . deserialize ( data , fingerprint ) ; return docMeta ; } /**
     * Convenience method to not require the fingerprint.
     */ public async writeDocMeta ( docMeta , datastoreMutation ? ) { Preconditions . assertPresent ( docMeta , "No docMeta" ) ; Preconditions . assertPresent ( docMeta . docInfo , "No docInfo on docMeta" ) ; Preconditions . assertPresent ( docMeta . docInfo . fingerprint , "No fingerprint on docInfo" ) ; docMeta . docInfo . uuid = UUIDs . create ( ) ; await this . writeDocMetaTags ( docMeta ) ; return this . write ( docMeta . docInfo . fingerprint , docMeta , { datastoreMutation } ) ; } private async writeDocMetaTags ( docMeta ) { try { const tags = DocMetaTags . toTags ( docMeta ) ; for ( const tag of tags ) { this . userTagsDB ?. registerWhenAbsent ( tag ) ; } await this . userTagsDB ?. commit ( ) ; log . debug ( "Wrote tags to TagsDB: " , tags ) ; } catch ( e ) { log . error ( "Failed to write docMeta tags: " , e ) ; } } /**
     * Write the datastore to disk.
     */ public async write ( fingerprint , docMeta , opts = { } ) { const datastoreMutation = opts . datastoreMutation || new DefaultDatastoreMutation ( ) ; Preconditions . assertPresent ( fingerprint , "fingerprint" ) ; Preconditions . assertPresent ( docMeta , "docMeta" ) ; if ( ! ( docMeta instanceof DocMeta ) ) { const msg = "Can not sync anything other than DocMeta" ; log . warn ( msg + ': ' , docMeta ) ; throw new Error ( msg ) ; } docMeta = Dictionaries . copyOf ( docMeta ) ; docMeta . docInfo . lastUpdated = ISODateTimeStrings . create ( ) ; docMeta . docInfo . nrComments = Object . values ( docMeta . pageMetas ) . map ( current => Dictionaries . countOf ( current . comments ) ) . reduce ( Reducers . SUM , 0 ) ; docMeta . docInfo . nrNotes = Object . values ( docMeta . pageMetas ) . map ( current => Dictionaries . countOf ( current . notes ) ) . reduce ( Reducers . SUM , 0 ) ; docMeta . docInfo . nrFlashcards = Object . values ( docMeta . pageMetas ) . map ( current => Dictionaries . countOf ( current . flashcards ) ) . reduce ( Reducers . SUM , 0 ) ; docMeta . docInfo . nrTextHighlights = Object . values ( docMeta . pageMetas ) . map ( current => Dictionaries . countOf ( current . textHighlights ) ) . reduce ( Reducers . SUM , 0 ) ; docMeta . docInfo . nrAreaHighlights = Object . values ( docMeta . pageMetas ) . map ( current => Dictionaries . countOf ( current . areaHighlights ) ) . reduce ( Reducers . SUM , 0 ) ; docMeta . docInfo . nrAnnotations = docMeta . docInfo . nrComments + docMeta . docInfo . nrNotes + docMeta . docInfo . nrFlashcards + docMeta . docInfo . nrTextHighlights + docMeta . docInfo . nrAreaHighlights ; if ( docMeta . docInfo . added === undefined ) { docMeta . docInfo . added = ISODateTimeStrings . create ( ) ; } docMeta . docInfo . uuid = UUIDs . create ( ) ; log . info ( `Sync of docMeta with fingerprint: ${ fingerprint } and UUID: ` + docMeta . docInfo . uuid ) ; const data = DocMetas . serialize ( docMeta , "" ) ; const docInfo = Object . assign ( { } , docMeta . docInfo ) ; const syncMutation = new DefaultDatastoreMutation < boolean > ( ) ; DatastoreMutations . pipe ( syncMutation , datastoreMutation , ( ) => docInfo ) ; const writeOpts = { ... opts , datastoreMutation : syncMutation , } ; await this . datastore . write ( fingerprint , data , docInfo , writeOpts ) ; return docInfo ; } public async synchronizeDocs ( ... docMetaRefs ) { return this . datastore . synchronizeDocs ( ... docMetaRefs ) ; } public getDocMetaRefs ( ) { return this . datastore . getDocMetaRefs ( ) ; } /**
     * Get a current snapshot of the internal state of the Datastore by
     * receiving DocMetaSnapshotEvent on the initial state.
     */ public snapshot ( listener , errorListener = NULL_FUNCTION ) { return this . datastore . snapshot ( listener , errorListener ) ; } public async createBackup ( ) { return this . datastore . createBackup ( ) ; } public writeFile ( backend , ref , data , opts ? ) { return this . datastore . writeFile ( backend , ref , data , opts ) ; } public containsFile ( backend , ref ) { return this . datastore . containsFile ( backend , ref ) ; } public getFile ( backend , ref , opts ? ) { return this . datastore . getFile ( backend , ref , opts ) ; } public deleteFile ( backend , ref ) { return this . datastore . deleteFile ( backend , ref ) ; } public addDocMetaSnapshotEventListener ( docMetaSnapshotEventListener ) { this . datastore . addDocMetaSnapshotEventListener ( docMetaSnapshotEventListener ) ; } public async overview ( ) { return await this . datastore . overview ( ) ; } public capabilities ( ) { return this . datastore . capabilities ( ) ; } public async deactivate ( ) { await this . datastore . deactivate ( ) ; } }