import { Optional } from "polar-shared/src/util/ts/Optional" ; import { DurationStr , TimeDurations } from 'polar-shared/src/util/TimeDurations' ; import { Preconditions } from "polar-shared/src/Preconditions" ; import { ISODateTimeString , ISODateTimeStrings } from "polar-shared/src/metadata/ISODateTimeStrings" ; import { PersistentPrefsUpdatedCallback , PrefsProvider } from "../../datastore/Datastore" ; import { OnErrorCallback , SnapshotUnsubscriber } from "polar-shared/src/util/Snapshots" ; export abstract class Prefs { /**
     * A marked pref is a pref that is boolean true or false
     */ public mark ( key , value = true ) { if ( value ) { this . set ( key , 'true' ) ; } else { this . set ( key , 'false' ) ; } } public toggleMarked ( key , value = false ) { this . mark ( key , ! this . isMarked ( key , value ) ) ; } public isMarked ( key , defaultValue = false ) { Preconditions . assertPresent ( key , "key" ) ; const currentValue = this . get ( key ) . getOrElse ( `${ defaultValue }` ) ; return currentValue === 'true' ; } public markDelayed ( key , duration = "1w" ) { const durationMS = TimeDurations . toMillis ( duration ) ; const after = Date . now ( ) + durationMS ; this . set ( key , `${ after }` ) ; } public isMarkedDelayed ( key ) { const val = this . get ( key ) . getOrElse ( '' ) ; if ( val . match ( /[0-9]+/ ) ) { return Date . now ( ) < parseInt ( val ) ; } return false ; } /**
     * Return true if the given pref is defined.
     */ public defined ( key ) { return this . get ( key ) . isPresent ( ) ; } /**
     * Get a current pref value.
     */ public abstract get ( key ) ; /**
     * Set a current pref value.
     */ public abstract set ( key , value ) ; public abstract toDict ( ) ; public abstract toPrefDict ( ) ; } export interface Pref { /**
     * The key for this pref
     */ readonly key : string ; /**
     * The value of this pref.
     */ readonly value : string ; /**
     * The time this pref was written.
     */ readonly written : ISODateTimeString ; } /**
 *
 * A prefs object that can be persisted to disk
 */ export interface IPersistentPrefs extends Prefs { /**
     * Update the current prefs with the source and return true
     * if any changes were made.
     */ update ( source ) : boolean ; fetch ( key ) : Pref | undefined ; /**
     * Get all the prefs.
     */ prefs ( ) : ReadonlyArray < Pref > ; /**
     * Commit this prefs.
     */ commit ( ) : Promise < void > ; } export interface InterceptedPersistentPrefs extends IPersistentPrefs { readonly __intercepted : true ; } export type CommitCallback = ( persistentPrefs : IPersistentPrefs ) => Promise < void > ; export class InterceptedPrefsProvider { constructor ( private readonly delegate , private readonly onCommit ) { } public get ( ) { return this . intercept ( this . delegate . get ( ) ) ! ; } public subscribe ( onNext , onError ) { const handleOnNext = ( persistentPrefs ) => { onNext ( this . intercept ( persistentPrefs ) ) ; } ; return this . delegate . subscribe ( handleOnNext , onError ) ; } private intercept ( persistentPrefs ) { if ( persistentPrefs ) { return InterceptedPersistentPrefsFactory . create ( persistentPrefs , this . onCommit ) ; } else { return undefined ; } } } export class InterceptedPersistentPrefsFactory { public static create ( persistentPrefs , onCommit ) { const commit = async ( ) => { await onCommit ( persistentPrefs ) ; await persistentPrefs . commit ( ) ; } ; return { ... persistentPrefs , update : persistentPrefs . update , fetch : persistentPrefs . fetch , prefs : persistentPrefs . prefs , mark : persistentPrefs . mark , isMarkedDelayed : persistentPrefs . isMarkedDelayed , toggleMarked : persistentPrefs . toggleMarked , isMarked : persistentPrefs . isMarked , markDelayed : persistentPrefs . markDelayed , get : persistentPrefs . get , set : persistentPrefs . set , toDict : persistentPrefs . toDict , toPrefDict : persistentPrefs . toPrefDict , defined : persistentPrefs . defined , __intercepted : true , commit } ; } } /**
 * Prefs object just backed by a local dictionary.
 */ export class DictionaryPrefs extends Prefs { protected delegate : StringToPrefDict = { } ; constructor ( delegate = { } ) { super ( ) ; this . update ( delegate ) ; } public update ( dict = { } ) { if ( ! dict ) { return false ; } const isInvalid = ( pref ) : boolean => { return typeof pref === 'string' ; } ; const needsUpdate = ( curr , next ) => { if ( curr ) { if ( ISODateTimeStrings . compare ( curr . written , next . written ) >= 0 ) { return false ; } } return true ; } ; let updated = false ; for ( const pref of Object . values ( dict ) ) { if ( isInvalid ( pref ) ) { continue ; } const curr = this . fetch ( pref . key ) ; if ( needsUpdate ( curr , pref ) ) { this . delegate [ pref . key ] = pref ; updated = true ; } } return updated ; } public get ( key ) { const pref = this . delegate [ key ] ; if ( pref ) { return Optional . of ( pref . value ) ; } return Optional . empty ( ) ; } public set ( key , value ) { const written = ISODateTimeStrings . create ( ) ; this . delegate [ key ] = { key , value , written } ; } public toDict ( ) { const result = { } ; for ( const current of Object . values ( this . delegate ) ) { result [ current . key ] = current . value ; } return result ; } public toPrefDict ( ) { return { ... this . delegate } ; } public fetch ( key ) { return Optional . of ( this . delegate [ key ] ) . getOrUndefined ( ) ; } public prefs ( ) { return Object . values ( this . delegate ) ; } } export class CompositePrefs implements IPersistentPrefs { /**
     * The primary delegate
     */ private delegate : IPersistentPrefs ; private delegates : ReadonlyArray < IPersistentPrefs > ; constructor ( delegates ) { this . delegate = Preconditions . assertPresent ( delegates [ 0 ] , 'delegate' ) ; this . delegates = delegates ; } public defined ( key ) { return this . delegate . defined ( key ) ; } public get ( key ) { return this . delegate . get ( key ) ; } public isMarked ( key , defaultValue ? ) { return this . delegate . isMarked ( key , defaultValue ) ; } public isMarkedDelayed ( key ) { return this . delegate . isMarkedDelayed ( key ) ; } public mark ( key , value ? ) { return this . delegate . mark ( key , value ) ; } public markDelayed ( key , duration ? ) { return this . delegate . markDelayed ( key , duration ) ; } public toDict ( ) { return this . delegate . toDict ( ) ; } public toPrefDict ( ) { return this . delegate . toPrefDict ( ) ; } public toggleMarked ( key , value ? ) { return this . toggleMarked ( key , value ) ; } public async commit ( ) { for ( const delegate of this . delegates ) { await this . delegate . commit ( ) ; } } public set ( key , value ) { for ( const delegate of this . delegates ) { this . delegate . set ( key , value ) ; } } public fetch ( key ) { return this . delegate . fetch ( key ) ; } public prefs ( ) { return this . delegate . prefs ( ) ; } public update ( dict ) { return this . delegate . update ( dict ) ; } } export class NonPersistentPrefs extends DictionaryPrefs implements IPersistentPrefs { public async commit ( ) { } } export class ListenablePersistentPrefs extends CompositePrefs implements IPersistentPrefs { public constructor ( private readonly backing , private readonly onUpdated ) { super ( [ backing ] ) ; } public async commit ( ) { this . onUpdated ( this ) ; return super . commit ( ) ; } } export interface StringToStringDict { [ key ] : string ; } export interface StringToPrefDict { [ key ] : Pref ; }