import angular from 'angular' ; import { Injectable } from 'angular-ts-decorators' ; import autobind from 'autobind-decorator' ; import { FailedLocalStorageError } from '../../../shared/errors/errors' ; import { StoreKey } from '../../../shared/store/store.enum' ; import { StoreContent , TraceLogItem } from '../../../shared/store/store.interface' ; import { StoreService } from '../../../shared/store/store.service' ; import { NativeStorageError , Table , TraceLogColumn } from './android-store.enum' ; @ autobind @ Injectable ( 'StoreService' ) export class AndroidStoreService extends StoreService { appRowId = 1 ; db : any ; dbName = 'xbs.db' ; idCol = 'id' ; nativeStorageKeys : string [ ] = [ StoreKey . AlternateSearchBarPosition , StoreKey . AutoFetchMetadata , StoreKey . CheckForAppUpdates , StoreKey . DarkModeEnabled , StoreKey . DefaultToFolderView , StoreKey . DisplayHelp , StoreKey . DisplayOtherSyncsWarning , StoreKey . DisplayPermissions , StoreKey . DisplayUpdated , StoreKey . LastUpgradeVersion , StoreKey . SyncBookmarksToolbar , StoreKey . SyncEnabled ] ; sqlKeys : string [ ] = [ StoreKey . Bookmarks , StoreKey . LastUpdated , StoreKey . Password , StoreKey . RemovedSync , StoreKey . ServiceUrl , StoreKey . SyncId , StoreKey . SyncVersion , StoreKey . TraceLog ] ; static $inject = [ '$q' ] ; protected addTraceLog ( log ) { return this . $q < void > ( ( resolve , reject ) => { this . openDatabase ( ) . then ( ( db ) => { db . executeSql ( `INSERT INTO ${ Table . TraceLog } (
            ${ TraceLogColumn . Timestamp },
            ${ TraceLogColumn . Level },
            ${ TraceLogColumn . Message }
          ) VALUES (?, ?, ?)` , [ log . timestamp , log . level , log . message ] , ( ) => resolve ( ) , reject ) ; } ) ; } ) . catch ( this . handleSqlError ) ; } protected clear ( ) { return this . $q . all ( [ this . $q ( ( resolve , reject ) => window . NativeStorage . clear ( resolve , reject ) ) , this . openDatabase ( ) . then ( this . createTables ) . catch ( this . handleSqlError ) ] ) . then ( ( ) => { } ) ; } protected clearTraceLog ( ) { return this . $q < void > ( ( resolve , reject ) => { this . openDatabase ( ) . then ( ( db ) => { db . executeSql ( `DELETE FROM ${ Table . TraceLog }` , [ ] , ( ) => resolve ( ) , reject ) ; } ) ; } ) . catch ( this . handleSqlError ) ; } protected createTables ( db ) { return this . $q ( ( resolve , reject ) => { db . transaction ( ( tx ) => { tx . executeSql ( `DROP TABLE IF EXISTS ${ Table . App }` ) ; tx . executeSql ( `CREATE TABLE ${ Table . App } (
            ${ this . idCol } INTEGER PRIMARY KEY,
            ${ StoreKey . Bookmarks } TEXT,
            ${ StoreKey . LastUpdated } TEXT,
            ${ StoreKey . Password } TEXT,
            ${ StoreKey . RemovedSync } TEXT,
            ${ StoreKey . ServiceUrl } TEXT,
            ${ StoreKey . SyncId } TEXT,
            ${ StoreKey . SyncVersion } TEXT
          )` ) ; tx . executeSql ( `INSERT INTO ${ Table . App } (${ this . idCol }) VALUES (?)` , [ this . appRowId ] ) ; tx . executeSql ( `DROP TABLE IF EXISTS ${ Table . TraceLog }` ) ; tx . executeSql ( `CREATE TABLE ${ Table . TraceLog } (
            ${ TraceLogColumn . Timestamp } INTEGER PRIMARY KEY,
            ${ TraceLogColumn . Level } INTEGER,
            ${ TraceLogColumn . Message } TEXT
          )` ) ; } , reject , resolve ) ; } ) . catch ( this . handleSqlError ) ; } protected getFromStore < T = StoreContent > ( keys = [ ] ) { return this . $q < T [ ] > ( ( resolve , reject ) => { const keysForNativeStorage = keys . filter ( ( key ) => this . nativeStorageKeys . includes ( key ) ) ; const keysForSql = keys . filter ( ( key ) => this . sqlKeys . includes ( key ) ) ; const results = new Array ( keys . length ) ; this . $q . all ( keysForNativeStorage . map ( ( key ) => { return this . getFromNativeStorage ( key ) . then ( ( value ) => { results [ keys . indexOf ( key ) ] = value ; } ) ; } ) . concat ( [ this . getFromSql ( keysForSql ) . then ( ( values ) => { keysForSql . forEach ( ( key , index ) => { results [ keys . indexOf ( key ) ] = values [ index ] ; } ) ; } ) ] ) ) . then ( ( ) => resolve ( results ) ) . catch ( reject ) ; } ) ; } protected getAllTraceLogs ( ) { return this . $q < TraceLogItem [ ] > ( ( resolve , reject ) => { this . openDatabase ( ) . then ( ( db ) => { db . executeSql ( `SELECT * FROM ${ Table . TraceLog } ORDER BY ${ TraceLogColumn . Timestamp }` , [ ] , ( result ) => { const logItems = [ ] ; for ( let x = 0 ; x < result . rows . length ; x += 1 ) { const logItem = { level : result . rows . item ( x ) . level , message : result . rows . item ( x ) . message , timestamp : result . rows . item ( x ) . timestamp } ; logItems . push ( logItem ) ; } resolve ( logItems ) ; } , reject ) ; } ) ; } ) . catch ( this . handleSqlError ) ; } protected getFromNativeStorage < T = StoreContent > ( key ) { return this . $q < T > ( ( resolve , reject ) => { if ( angular . isUndefined ( key ?? undefined ) ) { return resolve ( ) ; } const failure = ( err = new Error ( ) ) => { if ( ( err as any ) . code === NativeStorageError . ItemNotFound ) { return resolve ( ) ; } reject ( new FailedLocalStorageError ( NativeStorageError [ ( err as any ) . code ] , err ) ) ; } ; window . NativeStorage . getItem ( key , resolve , failure ) ; } ) ; } protected getFromSql < T = StoreContent > ( keys = [ ] ) { const values = new Array ( keys . length ) ; return this . $q < T [ ] > ( ( resolve , reject ) => { const keysWithoutTraceLog = keys . filter ( ( key ) => key !== StoreKey . TraceLog ) ; if ( keysWithoutTraceLog . length === 0 ) { return resolve ( [ ] ) ; } this . openDatabase ( ) . then ( ( db ) => { db . executeSql ( `SELECT ${ keysWithoutTraceLog . join ( ', ' ) } FROM ${ Table . App } WHERE ${ this . idCol } = ?` , [ this . appRowId ] , ( result ) => { keysWithoutTraceLog . forEach ( ( key ) => { values [ keys . indexOf ( key ) ] = result . rows . item ( 0 ) [ key as string ] ; } ) ; resolve ( ) ; } , reject ) ; } ) ; } ) . then ( ( ) => { if ( ! keys . includes ( StoreKey . TraceLog ) ) { return ; } return this . getAllTraceLogs ( ) . then ( ( traceLogItems ) => { values [ keys . indexOf ( StoreKey . TraceLog ) ] = traceLogItems ; } ) ; } ) . then ( ( ) => values ) . catch ( this . handleSqlError ) ; } protected handleSqlError ( err ) { throw new FailedLocalStorageError ( err . message ) ; } protected keys ( ) { return this . $q < string [ ] > ( ( resolve , reject ) => window . NativeStorage . keys ( resolve , reject ) ) . then ( ( keys ) => { return keys . concat ( this . sqlKeys ) ; } ) ; } protected openDatabase ( ) { if ( ! angular . isUndefined ( this . db ?? undefined ) ) { return this . $q . resolve ( this . db ) ; } return this . $q ( ( resolve , reject ) => { window . resolveLocalFileSystemURL ( window . cordova . file . externalDataDirectory , ( dataDir ) => { window . sqlitePlugin . openDatabase ( { name : this . dbName , androidDatabaseLocation : dataDir . toURL ( ) } , resolve , reject ) ; } ) ; } ) . then ( ( db ) => { this . db = db ; return db ; } ) . catch ( this . handleSqlError ) ; } protected removeFromStore ( keys = [ ] ) { const keysForNativeStorage = keys . filter ( ( key ) => this . nativeStorageKeys . includes ( key ) ) ; const keysForSql = keys . filter ( ( key ) => this . sqlKeys . includes ( key ) ) ; return this . $q . all ( keysForNativeStorage . map ( ( key ) => this . $q ( ( resolve , reject ) => window . NativeStorage . remove ( key , resolve , reject ) ) ) . concat ( this . $q . all ( keysForSql . map ( ( key ) => this . setInSql ( key , null ) ) ) ) ) . then ( ( ) => { } ) ; } protected setInStore ( key , value ) { if ( this . nativeStorageKeys . includes ( key ) ) { return this . setInNativeStorage ( key , value ) ; } return this . setInSql ( key , value ) ; } protected setInNativeStorage ( key , value ) { return this . $q ( ( resolve , reject ) => window . NativeStorage . setItem ( key , value , resolve , reject ) ) ; } protected setInSql ( key , value ) { if ( key === StoreKey . TraceLog ) { return angular . isUndefined ( value ?? undefined ) ? this . clearTraceLog ( ) : this . addTraceLog ( value ) ; } return this . $q < void > ( ( resolve , reject ) => { this . openDatabase ( ) . then ( ( db ) => { db . executeSql ( `UPDATE ${ Table . App } SET ${ key } = ? WHERE ${ this . idCol } = ?` , [ value , this . appRowId ] , ( ) => resolve ( ) , reject ) ; } ) ; } ) . catch ( this . handleSqlError ) ; } }