import { NoteDescriptor } from './NoteDescriptor' ; import { AddNoteClient , IAddNoteClient } from './clients/AddNoteClient' ; import { FindNotesClient , IFindNotesClient } from './clients/FindNotesClient' ; import { SyncQueue } from '../SyncQueue' ; import { Logger } from 'polar-shared/src/logger/Logger' ; import { IStoreMediaFileClient , MediaFile , StoreMediaFileClient } from './clients/StoreMediaFileClient' ; import { Dictionaries } from 'polar-shared/src/util/Dictionaries' ; import { MediaContents } from './MediaContents' ; import { AnkiFields } from './AnkiFields' ; import { CanAddNotesClient , ICanAddNotesClient } from './clients/CanAddNotesClient' ; import { SyncTaskResult } from '../SyncTask' ; import { Optional } from 'polar-shared/src/util/ts/Optional' ; import * as util from "util" ; import { SyncEntities } from "../../../../om/SyncEntities" ; const log = Logger . create ( ) ; /**
 * Performs sync of notes once we are certain the decks are created.
 */ export class NotesSync { public addNoteClient : IAddNoteClient = new AddNoteClient ( ) ; public canAddNotesClient : ICanAddNotesClient = new CanAddNotesClient ( ) ; public findNotesClient : IFindNotesClient = new FindNotesClient ( ) ; public storeMediaFileClient : IStoreMediaFileClient = new StoreMediaFileClient ( ) ; private readonly syncQueue : SyncQueue ; private results : NotesSynchronized = { created : [ ] } ; param syncQueue constructor ( syncQueue ) { this . syncQueue = syncQueue ; } param noteDescriptors public enqueue ( noteDescriptors ) { this . syncQueue . add ( async ( ) => { return await this . findNotes ( noteDescriptors ) ; } ) ; return this . results ; } private async findNotes ( noteDescriptors ) { const normalizedNotes = noteDescriptors . map ( current => this . normalize ( current ) ) ; normalizedNotes . forEach ( normalizedNote => { this . syncQueue . add ( async ( ) => { return await this . findNote ( normalizedNote ) ; } ) ; } ) ; const message = `Performing sync on ${ noteDescriptors . length } notes.` ; return Optional . of ( { message } ) ; } private async findNote ( normalizedNote ) { const polarGUID = NotesSync . createPolarID ( normalizedNote . noteDescriptor . guid ) ; const existingIDs = await this . findNotesClient . execute ( `tag:${ polarGUID . format ( ) }` ) ; const syncEntity = await SyncEntities . get ( 'anki' , normalizedNote . noteDescriptor . guid ) ; const hasExistingSyncEntity = syncEntity !== undefined ; if ( existingIDs . length === 0 && ! hasExistingSyncEntity ) { normalizedNote . noteDescriptor . tags . push ( "_polar-flashcard" ) ; this . syncQueue . add ( async ( ) => await this . canAddNote ( normalizedNote ) ) ; const message = `Note not found.  Checking if we can add.` ; log . debug ( message , normalizedNote ) ; return Optional . of ( { message } ) ; } else { const message = 'Note already found. Skipping.' ; log . debug ( message , normalizedNote ) ; return Optional . of ( { message } ) ; } } private async canAddNote ( normalizedNote ) { const canAddNotes = await this . canAddNotesClient . execute ( [ normalizedNote . noteDescriptor ] ) ; let message ; this . syncQueue . add ( async ( ) => await this . addNote ( normalizedNote ) ) ; if ( canAddNotes . length > 0 && canAddNotes [ 0 ] ) { message = 'Note can be added' ; } else { message = 'Note already exists' ; } log . debug ( message , normalizedNote ) ; return Optional . of ( { message } ) ; } private async storeMediaFile ( mediaFile ) { await this . storeMediaFileClient . execute ( mediaFile . filename , mediaFile . data ) ; return Optional . of ( { message : `Sync'd media file: ${ mediaFile . filename }` } ) ; } private async addNote ( normalizedNote ) { let message = `Added note and ${ normalizedNote . mediaFiles . length } media files.` ; try { normalizedNote . mediaFiles . forEach ( current => { this . syncQueue . add ( async ( ) => this . storeMediaFile ( current ) ) ; } ) ; const createdID = await this . addNoteClient . execute ( normalizedNote . noteDescriptor ) ; await SyncEntities . set ( 'anki' , normalizedNote . noteDescriptor . guid , `${ createdID }` ) ; this . results . created . push ( normalizedNote . noteDescriptor ) ; } catch ( err ) { message = "Failed to create note: " + this . pp ( normalizedNote . noteDescriptor ) ; log . warn ( message , err ) ; return Optional . of ( { message , failed : true } ) ; } return Optional . of ( { message } ) ; } private pp ( noteDescriptor ) { return util . inspect ( noteDescriptor , false , undefined , false ) ; } private normalize ( noteDescriptor ) { const mediaFiles = [ ] ; let fields = { } ; Dictionaries . forDict ( noteDescriptor . fields , ( key , value ) => { const mediaContent = MediaContents . parse ( value ) ; fields [ key ] = mediaContent . content ; mediaFiles . push ( ... mediaContent . mediaFiles ) ; } ) ; fields = AnkiFields . normalize ( fields ) ; const normalizedNoteDescriptor = { guid : noteDescriptor . guid , deckName : noteDescriptor . deckName , modelName : noteDescriptor . modelName , fields , tags : noteDescriptor . tags } ; return { noteDescriptor : normalizedNoteDescriptor , mediaFiles } ; } public static createPolarID ( guid ) { return new Tag ( 'polar_guid' , guid ) ; } } /**
 * A NoteDescriptor container which includes the normalize descriptor and also
 * the media.
 */ export interface NormalizedNote { readonly noteDescriptor : NoteDescriptor ; readonly mediaFiles : MediaFile [ ] ; } export interface ITag { readonly name : string ; readonly value : string ; } export class Tag implements ITag { public readonly name : string ; public readonly value : string ; constructor ( name , value ) { this . name = name ; this . value = value ; } public format ( ) { return `${ this . name }:${ this . value }` ; } } export interface NotesSynchronized { readonly created : NoteDescriptor [ ] ; }