import { Injectable } from '@angular/core' ; import { BrowserWindow , ipcRenderer , remote } from 'electron' ; import * as fs from 'fs-extra' ; import * as moment from 'moment' ; import { Duration , Moment } from 'moment' ; import * as path from 'path' ; import { Observable , Subject } from 'rxjs' ; import sanitize from 'sanitize-filename' ; import { Constants } from '../../core/constants' ; import { DateFormatter } from '../../core/date-formatter' ; import { Operation } from '../../core/enums' ; import { Logger } from '../../core/logger' ; import { NoteExport } from '../../core/note-export' ; import { Settings } from '../../core/settings' ; import { TasksCount } from '../../core/tasks-count' ; import { Utils } from '../../core/utils' ; import { DataStore } from '../../data/data-store' ; import { Note } from '../../data/entities/note' ; import { Notebook } from '../../data/entities/notebook' ; import { AppearanceService } from '../appearance/appearance.service' ; import { NoteDateFormatResult } from '../results/note-date-format-result' ; import { NoteDetailsResult } from '../results/note-details-result' ; import { NoteMarkResult } from '../results/note-mark-result' ; import { NoteOperationResult } from '../results/note-operation-result' ; import { NotesCountResult } from '../results/notes-count-result' ; import { SearchService } from '../search/search.service' ; import { TranslatorService } from '../translator/translator.service' ; @ Injectable ( { providedIn : 'root' , } ) export class CollectionService { private dataStore : DataStore = new DataStore ( ) ; private isInitializing : boolean = false ; private isInitialized : boolean = false ; private globalEmitter : any = remote . getGlobal ( 'globalEmitter' ) ; private openNoteIds : string [ ] = [ ] ; private collectionsChanged : Subject < void > = new Subject ( ) ; private notebookEdited : Subject < void > = new Subject ( ) ; private notebookDeleted : Subject < void > = new Subject ( ) ; private noteEdited : Subject < void > = new Subject ( ) ; private notesChanged : Subject < void > = new Subject ( ) ; private notesCountChanged : Subject < NotesCountResult > = new Subject < NotesCountResult > ( ) ; private noteMarkChanged : Subject < NoteMarkResult > = new Subject < NoteMarkResult > ( ) ; private noteNotebookChanged : Subject < void > = new Subject ( ) ; private setNoteOpenEventListener : any = this . setNoteOpenAsync . bind ( this ) ; private setNoteMarkEventListener : any = this . setNoteMark . bind ( this ) ; private setNotebookEventListener : any = this . setNotebook . bind ( this ) ; private getNoteDetailsEventListener : any = this . getNoteDetailsEventHandler . bind ( this ) ; private getNotebooksEventListener : any = this . getNotebooksEventHandler . bind ( this ) ; private setNoteTitleEventListener : any = this . setNoteTitleEventHandler . bind ( this ) ; private setNoteTextEventListener : any = this . setNoteTextEventHandler . bind ( this ) ; private deleteNoteEventListener : any = this . deleteNoteEventHandler . bind ( this ) ; private _activeCollection : string ; constructor ( private translator , private search , private appearance , private dateFormatter , private settings , private logger ) { } public collectionsChanged$ : Observable < void > = this . collectionsChanged . asObservable ( ) ; public notebookEdited$ : Observable < void > = this . notebookEdited . asObservable ( ) ; public notebookDeleted$ : Observable < void > = this . notebookDeleted . asObservable ( ) ; public noteEdited$ : Observable < void > = this . noteEdited . asObservable ( ) ; public notesChanged$ : Observable < void > = this . notesChanged . asObservable ( ) ; public notesCountChanged$ : Observable < NotesCountResult > = this . notesCountChanged . asObservable ( ) ; public noteMarkChanged$ : Observable < NoteMarkResult > = this . noteMarkChanged . asObservable ( ) ; public noteNotebookChanged$ : Observable < void > = this . noteNotebookChanged . asObservable ( ) ; public get activeCollection ( ) : string { return this . _activeCollection ; } public set activeCollection ( v ) { this . _activeCollection = v ; } private listenToNoteEvents ( ) { this . globalEmitter . removeListener ( Constants . setNoteOpenEvent , this . setNoteOpenEventListener ) ; this . globalEmitter . removeListener ( Constants . setNoteMarkEvent , this . setNoteMarkEventListener ) ; this . globalEmitter . removeListener ( Constants . setNotebookEvent , this . setNotebookEventListener ) ; this . globalEmitter . removeListener ( Constants . getNoteDetailsEvent , this . getNoteDetailsEventListener ) ; this . globalEmitter . removeListener ( Constants . getNotebooksEvent , this . getNotebooksEventListener ) ; this . globalEmitter . removeListener ( Constants . setNoteTitleEvent , this . setNoteTitleEventListener ) ; this . globalEmitter . removeListener ( Constants . setNoteTextEvent , this . setNoteTextEventListener ) ; this . globalEmitter . removeListener ( Constants . deleteNoteEvent , this . deleteNoteEventListener ) ; this . globalEmitter . on ( Constants . setNoteOpenEvent , this . setNoteOpenEventListener ) ; this . globalEmitter . on ( Constants . setNoteMarkEvent , this . setNoteMarkEventListener ) ; this . globalEmitter . on ( Constants . setNotebookEvent , this . setNotebookEventListener ) ; this . globalEmitter . on ( Constants . getNoteDetailsEvent , this . getNoteDetailsEventListener ) ; this . globalEmitter . on ( Constants . getNotebooksEvent , this . getNotebooksEventListener ) ; this . globalEmitter . on ( Constants . setNoteTitleEvent , this . setNoteTitleEventListener ) ; this . globalEmitter . on ( Constants . setNoteTextEvent , this . setNoteTextEventListener ) ; this . globalEmitter . on ( Constants . deleteNoteEvent , this . deleteNoteEventListener ) ; } public get hasStorageDirectory ( ) : boolean { const storageDirectory = this . settings . storageDirectory ; if ( ! storageDirectory ) { this . logger . info ( 'Storage directory setting is empty' , 'CollectionService' , 'hasStorageDirectory' ) ; return false ; } if ( ! fs . existsSync ( storageDirectory ) ) { this . logger . info ( `Storage directory '${ storageDirectory }' is not found on disk` , 'CollectionService' , 'hasStorageDirectory' ) ; return false ; } this . logger . info ( `Storage directory '${ storageDirectory }' is OK` , 'CollectionService' , 'hasStorageDirectory' ) ; return true ; } public async getCollectionsAsync ( ) { const storageDirectory = this . settings . storageDirectory ; const fileNames = await fs . readdir ( storageDirectory ) ; const collections = [ ] ; for ( const fileName of fileNames ) { const absoluteFilePath = path . join ( storageDirectory , fileName ) ; const stat = await fs . stat ( absoluteFilePath ) ; if ( stat . isDirectory ( ) ) { collections . push ( fileName ) ; } } return collections ; } public async setStorageDirectoryAsync ( parentDirectory ) { try { const storageDirectory = path . join ( parentDirectory , Constants . collectionsDirectory ) ; if ( ! ( await fs . pathExists ( storageDirectory ) ) ) { await fs . mkdir ( storageDirectory ) ; this . logger . info ( `Created storageDirectory '${ storageDirectory }' on disk` , 'CollectionService' , 'setStorageDirectoryAsync' ) ; } else { this . logger . info ( `StorageDirectory '${ storageDirectory }' already exists on disk. No need to create it.` , 'CollectionService' , 'setStorageDirectoryAsync' ) ; } this . settings . storageDirectory = storageDirectory ; this . logger . info ( `Saved storage directory '${ storageDirectory }' in settings store` , 'CollectionService' , 'setStorageDirectoryAsync' ) ; } catch ( error ) { this . logger . error ( `Could not create storage directory on disk. Cause: ${ error }` , 'CollectionService' , 'setStorageDirectoryAsync' ) ; return false ; } return true ; } public async initializeAsync ( ) { if ( this . isInitialized ) { return ; } if ( this . isInitializing ) { while ( this . isInitializing ) { await Utils . sleep ( 100 ) ; } return ; } this . isInitializing = true ; const storageDirectory = this . settings . storageDirectory ; let activeCollection = this . settings . activeCollection ; let activeCollectionDirectory = '' ; if ( activeCollection && Utils . collectionToPath ( storageDirectory , activeCollection ) . includes ( storageDirectory ) && Utils . collectionToPath ( storageDirectory , activeCollection ) !== storageDirectory && ( await fs . pathExists ( Utils . collectionToPath ( storageDirectory , activeCollection ) ) ) ) { activeCollectionDirectory = Utils . collectionToPath ( storageDirectory , activeCollection ) ; } else { const collections = await this . getCollectionsAsync ( ) ; if ( collections && collections . length > 0 ) { activeCollection = collections [ 0 ] ; } else { activeCollection = Constants . defaultCollection ; } activeCollectionDirectory = Utils . collectionToPath ( storageDirectory , activeCollection ) ; if ( ! ( await fs . pathExists ( activeCollectionDirectory ) ) ) { await fs . mkdir ( activeCollectionDirectory ) ; } } this . setActiveCollection ( activeCollection ) ; const databaseFile = path . join ( activeCollectionDirectory , `${ activeCollection }.db` ) ; await this . dataStore . initializeAsync ( databaseFile ) ; this . logger . info ( `Initialized data store: ${ databaseFile }` , 'CollectionService' , 'initializeAsync' ) ; this . listenToNoteEvents ( ) ; this . isInitializing = false ; this . isInitialized = true ; } private setActiveCollection ( activeCollection ) { this . activeCollection = activeCollection ; this . settings . activeCollection = activeCollection ; } public async addCollectionAsync ( possiblyDirtyCollection ) { if ( ! possiblyDirtyCollection ) { this . logger . error ( 'possiblyDirtyCollection is null' , 'CollectionService' , 'addCollectionAsync' ) ; return Operation . Error ; } const sanitizedCollection = sanitize ( possiblyDirtyCollection ) ; try { if ( await this . collectionExistsAsync ( sanitizedCollection ) ) { this . logger . info ( `Not adding collection '${ sanitizedCollection }' because it already exists` , 'CollectionService' , 'addCollectionAsync' ) ; return Operation . Duplicate ; } const storageDirectory = this . settings . storageDirectory ; await fs . mkdir ( Utils . collectionToPath ( storageDirectory , sanitizedCollection ) ) ; this . logger . info ( `Added collection '${ sanitizedCollection }'` , 'CollectionService' , 'addCollectionAsync' ) ; this . settings . activeCollection = sanitizedCollection ; } catch ( error ) { this . logger . error ( `Could not add collection '${ sanitizedCollection }'. Cause: ${ error }` , 'CollectionService' , 'addCollectionAsync' ) ; return Operation . Error ; } this . isInitialized = false ; this . collectionsChanged . next ( ) ; return Operation . Success ; } public async renameCollectionAsync ( initialCollection , finalCollection ) { if ( ! finalCollection ) { this . logger . error ( 'finalCollection is null' , 'CollectionService' , 'renameCollectionAsync' ) ; return Operation . Error ; } if ( initialCollection . toLowerCase ( ) === finalCollection . toLowerCase ( ) ) { return Operation . Aborted ; } try { if ( await this . collectionExistsAsync ( finalCollection ) ) { return Operation . Duplicate ; } const storageDirectory = this . settings . storageDirectory ; await fs . move ( path . join ( Utils . collectionToPath ( storageDirectory , initialCollection ) , `${ initialCollection }.db` ) , path . join ( Utils . collectionToPath ( storageDirectory , initialCollection ) , `${ finalCollection }.db` ) ) ; await fs . move ( Utils . collectionToPath ( storageDirectory , initialCollection ) , Utils . collectionToPath ( storageDirectory , finalCollection ) ) ; this . settings . activeCollection = finalCollection ; } catch ( error ) { this . logger . error ( `Could not rename the collection '${ initialCollection }' to '${ finalCollection }'. Cause: ${ error }` , 'CollectionService' , 'renameCollectionAsync' ) ; return Operation . Error ; } this . isInitialized = false ; this . collectionsChanged . next ( ) ; return Operation . Success ; } public async deleteCollectionAsync ( collection ) { try { const storageDirectory = this . settings . storageDirectory ; await fs . remove ( Utils . collectionToPath ( storageDirectory , collection ) ) ; const collections = await this . getCollectionsAsync ( ) ; if ( collections && collections . length > 0 ) { this . settings . activeCollection = collections [ 0 ] ; } else { this . settings . activeCollection = '' ; } } catch ( error ) { this . logger . error ( `Could not delete the collection '${ collection }'. Cause: ${ error }` , 'CollectionService' , 'deleteCollectionAsync' ) ; } this . isInitialized = false ; this . collectionsChanged . next ( ) ; return Operation . Success ; } public activateCollection ( collection ) { this . setActiveCollection ( collection ) ; this . isInitialized = false ; this . collectionsChanged . next ( ) ; } public getActiveCollection ( ) { return this . settings . activeCollection ; } public noteIsOpen ( noteId ) { if ( this . openNoteIds . includes ( noteId ) ) { return true ; } return false ; } public hasOpenNotes ( ) { return this . openNoteIds . length > 0 ; } public async getNotebooksAsync ( includeAllNotes ) { const notebooks = [ ] ; try { if ( includeAllNotes ) { const allNotesNotebook = new Notebook ( await this . translator . getAsync ( 'MainPage.AllNotes' ) ) ; allNotesNotebook . id = Constants . allNotesNotebookId ; allNotesNotebook . isDefault = true ; notebooks . push ( allNotesNotebook ) ; } const unfiledNotesNotebook = new Notebook ( await this . translator . getAsync ( 'MainPage.UnfiledNotes' ) ) ; unfiledNotesNotebook . id = Constants . unfiledNotesNotebookId ; unfiledNotesNotebook . isDefault = true ; notebooks . push ( unfiledNotesNotebook ) ; const userNotebooks = this . dataStore . getNotebooks ( ) ; notebooks . push . apply ( notebooks , userNotebooks ) ; } catch ( error ) { this . logger . error ( `Could not get notebooks. Cause: ${ error }` , 'CollectionService' , 'getNotebooksAsync' ) ; } return notebooks ; } public addNotebook ( notebookName ) { if ( ! notebookName ) { this . logger . error ( 'notebookName is null' , 'CollectionService' , 'addNotebook' ) ; return Operation . Error ; } try { if ( this . notebookExists ( notebookName ) ) { this . logger . info ( `Not adding notebook '${ notebookName }' to the data store because it already exists` , 'CollectionService' , 'addNotebook' ) ; return Operation . Duplicate ; } this . dataStore . addNotebook ( notebookName ) ; this . logger . info ( `Added notebook '${ notebookName }' to the data store` , 'CollectionService' , 'addNotebook' ) ; } catch ( error ) { this . logger . error ( `Could not add notebook '${ notebookName }'. Cause: ${ error }` , 'CollectionService' , 'addNotebook' ) ; return Operation . Error ; } this . notebookEdited . next ( ) ; return Operation . Success ; } public async renameNotebookAsync ( notebookId , newNotebookName ) { if ( ! newNotebookName ) { this . logger . error ( 'newNotebookName is null' , 'CollectionService' , 'renameNotebookAsync' ) ; return Operation . Error ; } try { if ( this . notebookExists ( newNotebookName ) ) { return Operation . Duplicate ; } const notebook = this . dataStore . getNotebookById ( notebookId ) ; if ( notebook . name === newNotebookName ) { return Operation . Aborted ; } notebook . name = newNotebookName ; this . dataStore . updateNotebook ( notebook ) ; } catch ( error ) { this . logger . error ( `Could not rename the notebook with id='${ notebookId }' to '${ newNotebookName }'. Cause: ${ error }` , 'CollectionService' , 'renameNotebookAsync' ) ; return Operation . Error ; } this . notebookEdited . next ( ) ; return Operation . Success ; } public getNotebookName ( notebookId ) { return this . dataStore . getNotebookById ( notebookId ) . name ; } public async deleteNotebooksAsync ( notebookIds ) { let operation = Operation . Success ; for ( const notebookId of notebookIds ) { try { this . dataStore . deleteNotebook ( notebookId ) ; } catch ( error ) { this . logger . error ( `Could not delete the notebook with id='${ notebookId }'. Cause: ${ error }` , 'CollectionService' , 'deleteNotebooksAsync' ) ; operation = Operation . Error ; } } this . notebookDeleted . next ( ) ; return operation ; } private deleteNotePermanently ( noteId ) { this . dataStore . deleteNote ( noteId ) ; const notePath = this . getNotePath ( noteId ) ; const noteFilePath = path . join ( notePath , `${ noteId }${ Constants . noteContentExtension }` ) ; const noteStateFilePath = path . join ( notePath , `${ noteId }${ Constants . noteStateExtension }` ) ; fs . unlinkSync ( noteFilePath ) ; if ( fs . existsSync ( noteStateFilePath ) ) { fs . unlinkSync ( noteStateFilePath ) ; } } public deleteNotesPermanently ( noteIds ) { let operation = Operation . Success ; for ( const noteId of noteIds ) { try { this . deleteNotePermanently ( noteId ) ; } catch ( error ) { this . logger . error ( `Could not permanently delete the note with id='${ noteId }'. Cause: ${ error }` , 'CollectionService' , 'deleteNotesPermanentlyAsync' ) ; operation = Operation . Error ; } } this . notesChanged . next ( ) ; return operation ; } public deleteNotes ( noteIds ) { let operation = Operation . Success ; for ( const noteId of noteIds ) { if ( this . settings . moveDeletedNotesToTrash ) { try { this . dataStore . trashNote ( noteId ) ; } catch ( error ) { this . logger . error ( `Could not move the the note with id='${ noteId }' to the trash. Cause: ${ error }` , 'CollectionService' , 'deleteNotes' ) ; operation = Operation . Error ; } } else { try { this . deleteNotePermanently ( noteId ) ; } catch ( error ) { this . logger . error ( `Could not delete the note with id='${ noteId }'. Cause: ${ error }` , 'CollectionService' , 'deleteNotesAsync' ) ; operation = Operation . Error ; } } } this . notesChanged . next ( ) ; return operation ; } public restoreNotes ( noteIds ) { let operation = Operation . Success ; for ( const noteId of noteIds ) { try { this . dataStore . restoreNote ( noteId ) ; } catch ( error ) { this . logger . error ( `Could not restore the note with id='${ noteId }'. Cause: ${ error }` , 'CollectionService' , 'restoreNotes' ) ; operation = Operation . Error ; } } this . notesChanged . next ( ) ; return operation ; } public async getNotesAsync ( notebookId , category , useExactDates ) { const notesCountResult = new NotesCountResult ( ) ; let notes = [ ] ; try { let uncategorizedNotes = [ ] ; if ( notebookId === Constants . allNotesNotebookId ) { uncategorizedNotes = this . dataStore . getNotes ( ) ; } else if ( notebookId === Constants . unfiledNotesNotebookId ) { uncategorizedNotes = this . dataStore . getUnfiledNotes ( ) ; } else { uncategorizedNotes = this . dataStore . getNotebookNotes ( notebookId ) ; } uncategorizedNotes = this . getFilteredNotes ( uncategorizedNotes , this . search . searchText ) ; notesCountResult . allNotesCount = uncategorizedNotes . length ; const markedNotes = uncategorizedNotes . filter ( ( x ) => x . isMarked ) ; notesCountResult . markedNotesCount = markedNotes . length ; if ( category === Constants . markedCategory ) { notes = markedNotes ; } for ( const note of uncategorizedNotes ) { if ( category === Constants . allCategory ) { notes . push ( note ) ; } const result = await this . getNoteDateFormatAsync ( note . modificationDate , useExactDates ) ; if ( result . isTodayNote ) { if ( category === Constants . todayCategory ) { notes . push ( note ) ; } notesCountResult . todayNotesCount ++ ; } if ( result . isYesterdayNote ) { if ( category === Constants . yesterdayCategory ) { notes . push ( note ) ; } notesCountResult . yesterdayNotesCount ++ ; } if ( result . isThisWeekNote ) { if ( category === Constants . thisWeekCategory ) { notes . push ( note ) ; } notesCountResult . thisWeekNotesCount ++ ; } note . displayModificationDate = result . dateText ; note . displayExactModificationDate = this . dateFormatter . getFormattedDate ( note . modificationDate ) ; } this . notesCountChanged . next ( notesCountResult ) ; } catch ( error ) { this . logger . error ( `Could not get notes. Cause: ${ error }` , 'CollectionService' , 'getNotesAsync' ) ; } return notes ; } public addNote ( baseTitle , notebookId ) { let uniqueTitle = '' ; const result = new NoteOperationResult ( Operation . Success ) ; if ( notebookId === Constants . allNotesNotebookId || notebookId === Constants . unfiledNotesNotebookId ) { notebookId = '' ; } try { uniqueTitle = this . getUniqueNewNoteNoteTitle ( baseTitle ) ; result . noteId = this . dataStore . addNote ( uniqueTitle , notebookId ) ; const activeCollection = this . settings . activeCollection ; const storageDirectory = this . settings . storageDirectory ; fs . writeFileSync ( path . join ( Utils . collectionToPath ( storageDirectory , activeCollection ) , `${ result . noteId }${ Constants . noteContentExtension }` ) , '' ) ; this . noteEdited . next ( ) ; } catch ( error ) { this . logger . error ( `Could not add note '${ uniqueTitle }'. Cause: ${ error }` , 'CollectionService' , 'addNote' ) ; result . operation = Operation . Error ; } return result ; } public getNote ( noteId ) { return this . dataStore . getNoteById ( noteId ) ; } public async getNotebookAsync ( noteId ) { const note = this . dataStore . getNoteById ( noteId ) ; let notebook = this . dataStore . getNotebookById ( note . notebookId ) ; if ( ! note . notebookId || ! notebook ) { notebook = new Notebook ( await this . translator . getAsync ( 'MainPage.UnfiledNotes' ) ) ; } return notebook ; } public setNoteMark ( noteId , isMarked ) { const note = this . dataStore . getNoteById ( noteId ) ; note . isMarked = isMarked ; this . dataStore . updateNote ( note ) ; const markedNotes = this . dataStore . getMarkedNotes ( ) ; const result = new NoteMarkResult ( noteId , note . isMarked , markedNotes . length ) ; this . noteMarkChanged . next ( result ) ; this . globalEmitter . emit ( Constants . noteMarkChangedEvent , note . id , note . isMarked ) ; } public setNotebook ( notebookId , noteIds ) { let setNotebookOperation = Operation . Success ; for ( const noteId of noteIds ) { try { const note = this . dataStore . getNoteById ( noteId ) ; if ( notebookId === Constants . allNotesNotebookId || notebookId === note . notebookId ) { continue ; } if ( notebookId === Constants . unfiledNotesNotebookId ) { notebookId = '' ; } note . notebookId = notebookId ; this . dataStore . updateNote ( note ) ; this . sendNotebookNameAsync ( noteId ) ; } catch ( error ) { this . logger . error ( `Could not set the notebook for the note with id='${ noteId }' to notebook with id='${ notebookId }'. Cause: ${ error }` , 'CollectionService' , 'setNotebook' ) ; setNotebookOperation = Operation . Error ; } } this . noteNotebookChanged . next ( ) ; return setNotebookOperation ; } public setNoteTitleEventHandler ( noteId , initialNoteTitle , finalNoteTitle , callback ) { let uniqueNoteTitle = finalNoteTitle . trim ( ) ; if ( uniqueNoteTitle . length === 0 ) { callback ( new NoteOperationResult ( Operation . Blank ) ) ; return ; } if ( initialNoteTitle !== uniqueNoteTitle ) { try { uniqueNoteTitle = this . getUniqueNoteNoteTitle ( finalNoteTitle ) ; const note = this . dataStore . getNoteById ( noteId ) ; if ( note ) { note . title = uniqueNoteTitle ; this . dataStore . updateNote ( note ) ; this . logger . info ( `Renamed note with id=${ noteId } from ${ initialNoteTitle } to ${ uniqueNoteTitle }.` , 'CollectionService' , 'setNoteTitleEventHandler' ) ; } else { this . logger . warn ( `Note with id=${ noteId } could not be found. It was probably deleted.` , 'CollectionService' , 'setNoteTitleEventHandler' ) ; } } catch ( error ) { this . logger . error ( `Could not rename the note with id='${ noteId }' to '${ uniqueNoteTitle }'. Cause: ${ error }` , 'CollectionService' , 'setNoteTitleEventHandler' ) ; callback ( new NoteOperationResult ( Operation . Error ) ) ; return ; } } else { this . logger . info ( 'Final title is the same as initial title. No rename required.' , 'CollectionService' , 'setNoteTitleEventHandler' ) ; } const result = new NoteOperationResult ( Operation . Success ) ; result . noteId = noteId ; result . noteTitle = uniqueNoteTitle ; this . noteEdited . next ( ) ; callback ( result ) ; } public setNoteTextEventHandler ( noteId , noteText , tasksCount , callback ) { try { const note = this . dataStore . getNoteById ( noteId ) ; if ( note ) { note . text = noteText ; note . closedTasksCount = tasksCount . closedTasksCount ; note . totalTasksCount = tasksCount . totalTasksCount ; this . dataStore . updateNote ( note ) ; this . logger . info ( `Set text of note with id=${ noteId }.` , 'CollectionService' , 'setNoteTextEventHandler' ) ; } else { this . logger . warn ( `Note with id=${ noteId } could not be found. It was probably deleted.` , 'CollectionService' , 'setNoteTextEventHandler' ) ; } } catch ( error ) { this . logger . error ( `Could not set text for the note with id='${ noteId }' in the data store. Cause: ${ error }` , 'CollectionService' , 'setNoteTextEventHandler' ) ; callback ( Operation . Error ) ; return ; } this . noteEdited . next ( ) ; callback ( Operation . Success ) ; return ; } public deleteNoteEventHandler ( noteId ) { this . deleteNotes ( [ noteId ] ) ; } public async importFromOldVersionAsync ( directoryContainingExportFiles ) { const notebooksExportFile = path . join ( directoryContainingExportFiles , 'Notebooks.json' ) ; const notesExportFile = path . join ( directoryContainingExportFiles , 'Notes.json' ) ; let isImportSuccessful = true ; try { try { if ( await fs . pathExists ( notebooksExportFile ) ) { const notebooksJson = await fs . readFile ( notebooksExportFile , 'utf8' ) ; const jsonNotebooks = JSON . parse ( notebooksJson ) ; this . logger . info ( `${ notebooksExportFile } was found. Importing notebooks.` , 'CollectionService' , 'importFromOldVersionAsync' ) ; for ( const jsonNotebook of jsonNotebooks ) { try { if ( ! this . notebookExists ( jsonNotebook . Name ) ) { this . dataStore . addNotebook ( jsonNotebook . Name ) ; } } catch ( error ) { this . logger . error ( `An error occurred while importing a notebook from an old version. Cause: ${ error }` , 'CollectionService' , 'importFromOldVersionAsync' ) ; isImportSuccessful = false ; } } } else { this . logger . info ( `${ notebooksExportFile } was not found. Not importing notebooks.` , 'CollectionService' , 'importFromOldVersionAsync' ) ; } } catch ( error ) { this . logger . error ( `An error occurred while importing notebooks from an old version. Cause: ${ error }` , 'CollectionService' , 'importFromOldVersionAsync' ) ; isImportSuccessful = false ; } try { if ( await fs . pathExists ( notesExportFile ) ) { const notesJson = await fs . readFile ( notesExportFile , 'utf8' ) ; const jsonNotes = JSON . parse ( notesJson ) ; this . logger . info ( `${ notesExportFile } was found. Importing notes.` , 'CollectionService' , 'importFromOldVersionAsync' ) ; for ( const jsonNote of jsonNotes ) { try { if ( ! this . notebookExists ( jsonNote . Title ) ) { let notebookId = '' ; try { if ( jsonNote . Notebook ) { const notebook = this . dataStore . getNotebookByName ( jsonNote . Notebook ) ; if ( notebook ) { notebookId = notebook . id ; } } } catch ( error ) { this . logger . error ( `An error occurred while trying to find a notebook for a note. Cause: ${ error }` , 'CollectionService' , 'importFromOldVersionAsync' ) ; } this . dataStore . addNote ( jsonNote . Title , notebookId ) ; const note = this . dataStore . getNoteByTitle ( jsonNote . Title ) ; note . text = jsonNote . Text ; note . creationDate = moment ( jsonNote . CreationDate , 'YYYY-MM-DD HH:mm:ss' ) . valueOf ( ) ; note . modificationDate = moment ( jsonNote . ModificationDate , 'YYYY-MM-DD HH:mm:ss' ) . valueOf ( ) ; note . isMarked = jsonNote . IsMarked ; this . dataStore . updateNoteWithoutDate ( note ) ; const quillText = `{"ops":[{"insert":${ JSON . stringify ( jsonNote . Text ) }}]}` ; const activeCollection = this . settings . activeCollection ; const storageDirectory = this . settings . storageDirectory ; await fs . writeFile ( path . join ( Utils . collectionToPath ( storageDirectory , activeCollection ) , `${ note . id }${ Constants . noteContentExtension }` ) , quillText ) ; } } catch ( error ) { this . logger . error ( `An error occurred while importing a note from an old version. Cause: ${ error }` , 'CollectionService' , 'importFromOldVersionAsync' ) ; isImportSuccessful = false ; try { const note = this . dataStore . getNoteByTitle ( jsonNote . Title ) ; if ( note ) { this . dataStore . deleteNote ( note . id ) ; } } catch ( error ) { this . logger . error ( `Could note delete note from data store. Cause: ${ error }` , 'CollectionService' , 'importFromOldVersionAsync' ) ; } } } } else { this . logger . info ( `${ notesExportFile } was not found. Not importing notes.` , 'CollectionService' , 'importFromOldVersionAsync' ) ; } } catch ( error ) { this . logger . error ( `An error occurred while importing notes from an old version. Cause: ${ error }` , 'CollectionService' , 'importFromOldVersionAsync' ) ; isImportSuccessful = false ; } } catch ( error ) { this . logger . error ( `An error occurred while importing notebooks and/or notes from an old version. Cause: ${ error }` , 'CollectionService' , 'importFromOldVersionAsync' ) ; isImportSuccessful = false ; } return isImportSuccessful ; } public async importNoteFilesAsync ( noteFilePaths , notebookId = null ) { let numberofImportedNoteFiles = 0 ; let operation = Operation . Success ; for ( const noteFilePath of noteFilePaths ) { try { const noteFileContent = await fs . readFile ( noteFilePath , 'utf8' ) ; const noteExport = JSON . parse ( noteFileContent ) ; const proposedNoteTitle = `${ noteExport . title } (${ await this . translator . getAsync ( 'Notes.Imported' ) })` ; const uniqueNoteTitle = this . getUniqueNoteNoteTitle ( proposedNoteTitle ) ; this . dataStore . addNote ( uniqueNoteTitle , '' ) ; const note = this . dataStore . getNoteByTitle ( uniqueNoteTitle ) ; note . text = noteExport . text ; if ( notebookId && notebookId !== Constants . allNotesNotebookId && notebookId !== Constants . unfiledNotesNotebookId ) { note . notebookId = notebookId ; } this . dataStore . updateNoteWithoutDate ( note ) ; const activeCollection = this . settings . activeCollection ; const storageDirectory = this . settings . storageDirectory ; await fs . writeFile ( path . join ( Utils . collectionToPath ( storageDirectory , activeCollection ) , `${ note . id }${ Constants . noteContentExtension }` ) , noteExport . content ) ; numberofImportedNoteFiles ++ ; } catch ( error ) { this . logger . error ( `An error occurred while importing note file '${ noteFilePath }'. Cause: ${ error }` , 'CollectionService' , 'importNoteFilesAsync' ) ; operation = Operation . Error ; } } if ( numberofImportedNoteFiles > 0 ) { this . noteEdited . next ( ) ; } return operation ; } public getTrashedNotes ( ) { const trashedNotes = this . dataStore . getTrashedNotes ( ) ; if ( trashedNotes == undefined ) { return [ ] ; } for ( const trashedNote of trashedNotes ) { trashedNote . isSelected = false ; trashedNote . displayTrashedDate = this . dateFormatter . getFormattedDate ( trashedNote . trashedDate ) ; } return trashedNotes ; } private getNotePath ( noteId ) { const activeCollection = this . settings . activeCollection ; const storageDirectory = this . settings . storageDirectory ; return Utils . collectionToPath ( storageDirectory , activeCollection ) ; } private async collectionExistsAsync ( collection ) { const collections = await this . getCollectionsAsync ( ) ; const existingCollections = collections . filter ( ( x ) => x . toLowerCase ( ) === collection . toLowerCase ( ) ) ; return existingCollections && existingCollections . length > 0 ; } private async getNoteDetailsEventHandler ( noteId , callback ) { const note = this . dataStore . getNoteById ( noteId ) ; let notebookName = await this . translator . getAsync ( 'MainPage.UnfiledNotes' ) ; if ( note . notebookId ) { const notebook = this . dataStore . getNotebookById ( note . notebookId ) ; if ( notebook ) { notebookName = notebook . name ; } } callback ( new NoteDetailsResult ( note . title , notebookName , note . isMarked ) ) ; } private async sendNotebookNameAsync ( noteId ) { const note = this . dataStore . getNoteById ( noteId ) ; let notebookName = await this . translator . getAsync ( 'MainPage.UnfiledNotes' ) ; if ( note . notebookId ) { const notebook = this . dataStore . getNotebookById ( note . notebookId ) ; if ( notebook ) { notebookName = notebook . name ; } } this . globalEmitter . emit ( Constants . notebookChangedEvent , noteId , notebookName ) ; } private async getNotebooksEventHandler ( callback ) { const notebooks = await this . getNotebooksAsync ( false ) ; callback ( notebooks ) ; } private async setNoteOpenAsync ( noteId , isOpen ) { if ( isOpen ) { if ( ! this . openNoteIds . includes ( noteId ) ) { this . openNoteIds . push ( noteId ) ; const notePath = this . getNotePath ( noteId ) ; this . logger . info ( `note directory=${ notePath }` , 'CollectionService' , 'importNoteFilesAsync' ) ; const window = remote . getCurrentWindow ( ) ; const arg = { notePath : notePath , noteId : noteId , windowHasFrame : this . appearance . windowHasNativeTitleBar } ; ipcRenderer . send ( 'open-note-window' , arg ) ; } } else { if ( this . openNoteIds . includes ( noteId ) ) { this . openNoteIds . splice ( this . openNoteIds . indexOf ( noteId ) , 1 ) ; } } } private getUniqueNewNoteNoteTitle ( baseTitle ) { let counter = 1 ; let uniqueTitle = `${ baseTitle } ${ counter }` ; const notesWithIdenticalBaseTitle = this . dataStore . getNotesWithIdenticalBaseTitle ( baseTitle ) ; const similarTitles = notesWithIdenticalBaseTitle . map ( ( x ) => x . title ) ; while ( similarTitles . includes ( uniqueTitle ) ) { counter ++ ; uniqueTitle = `${ baseTitle } ${ counter }` ; } return uniqueTitle ; } private getUniqueNoteNoteTitle ( baseTitle ) { let counter = 0 ; let uniqueTitle = baseTitle ; const notesWithIdenticalBaseTitle = this . dataStore . getNotesWithIdenticalBaseTitle ( baseTitle ) ; const similarTitles = notesWithIdenticalBaseTitle . map ( ( x ) => x . title ) ; while ( similarTitles . includes ( uniqueTitle ) ) { counter ++ ; uniqueTitle = `${ baseTitle } (${ counter })` ; } return uniqueTitle ; } private notebookExists ( notebookName ) { const notebook = this . dataStore . getNotebookByName ( notebookName ) ; return notebook != undefined ; } private noteExists ( noteTitle ) { const note = this . dataStore . getNoteByTitle ( noteTitle ) ; return note != undefined ; } private async getNoteDateFormatAsync ( millisecondsSinceEpoch , useExactDates ) { const result = new NoteDateFormatResult ( ) ; const nowDateonly = moment ( ) . startOf ( 'day' ) ; const modificationDateOnly = moment ( millisecondsSinceEpoch ) . startOf ( 'day' ) ; const duration = moment . duration ( nowDateonly . diff ( modificationDateOnly ) ) ; if ( duration . asMonths ( ) >= 12 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.LongAgo' ) ; } else if ( duration . asMonths ( ) >= 11 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 11 } ) ; } else if ( duration . asMonths ( ) >= 10 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 10 } ) ; } else if ( duration . asMonths ( ) >= 9 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 9 } ) ; } else if ( duration . asMonths ( ) >= 8 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 8 } ) ; } else if ( duration . asMonths ( ) >= 7 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 7 } ) ; } else if ( duration . asMonths ( ) >= 6 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 6 } ) ; } else if ( duration . asMonths ( ) >= 5 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 5 } ) ; } else if ( duration . asMonths ( ) >= 4 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 4 } ) ; } else if ( duration . asMonths ( ) >= 3 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 3 } ) ; } else if ( duration . asMonths ( ) >= 2 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 2 } ) ; } else if ( duration . asMonths ( ) >= 1 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.MonthsAgo' , { count : 1 } ) ; } else if ( duration . asDays ( ) >= 21 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.WeeksAgo' , { count : 3 } ) ; } else if ( duration . asDays ( ) >= 14 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.WeeksAgo' , { count : 2 } ) ; } else if ( duration . asDays ( ) >= 8 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.LastWeek' ) ; } else if ( duration . asDays ( ) >= 7 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.DaysAgo' , { count : 7 } ) ; result . isThisWeekNote = true ; } else if ( duration . asDays ( ) >= 6 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.DaysAgo' , { count : 6 } ) ; result . isThisWeekNote = true ; } else if ( duration . asDays ( ) >= 5 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.DaysAgo' , { count : 5 } ) ; result . isThisWeekNote = true ; } else if ( duration . asDays ( ) >= 4 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.DaysAgo' , { count : 4 } ) ; result . isThisWeekNote = true ; } else if ( duration . asDays ( ) >= 3 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.DaysAgo' , { count : 3 } ) ; result . isThisWeekNote = true ; } else if ( duration . asDays ( ) >= 2 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.DaysAgo' , { count : 2 } ) ; result . isThisWeekNote = true ; } else if ( duration . asDays ( ) >= 1 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.Yesterday' ) ; result . isYesterdayNote = true ; result . isThisWeekNote = true ; } else if ( duration . asDays ( ) >= 0 ) { result . dateText = await this . translator . getAsync ( 'NoteDates.Today' ) ; result . isTodayNote = true ; result . isThisWeekNote = true ; } if ( useExactDates ) { result . dateText = this . dateFormatter . getFormattedDate ( millisecondsSinceEpoch ) ; } return result ; } private getFilteredNotes ( unfilteredNotes , filter ) { if ( ! filter || filter . trim ( ) . length === 0 ) { return unfilteredNotes ; } const searchTextPieces = filter . trim ( ) . split ( ' ' ) ; return unfilteredNotes . filter ( ( x ) => Utils . containsAll ( `${ x . title } ${ x . text }` , searchTextPieces ) ) ; } }