import { Injectable } from '@angular/core' ; import { Response } from '@angular/http' ; import { Observable } from 'rxjs/Observable' ; import "rxjs/Rx" ; import 'rxjs/add/operator/map' ; import { Transfer , TransferObject } from '@ionic-native/transfer' ; import { File } from '@ionic-native/file' ; import _ from "lodash" ; import { SafeHttpProvider } from "./safe-http" ; import { Chapter } from "../common/models/chapter" ; import { Novel } from "../common/models/novel" ; import { NovelsLocalService } from "./novels-local-service" ; import { DownloadItem , DownloadStatus , DownloadChapterItem } from "../common/models/download-item" ; import { SettingsService } from "./settings-service" ; @ Injectable ( ) export class DownloadService { fileTransfer : TransferObject ; queue : DownloadItem [ ] = [ ] ; novelsDir : string = "" ; novelsDirName : string = "novels" ; constructor ( private http , public file , private transfer , private novelsLocalService , private settingsService ) { console . log ( 'Hello Downloads Service' ) ; } init ( ) { this . fileTransfer = this . transfer . create ( ) ; this . novelsDir = `${ this . settingsService . settings . appStorageLocation }${ this . novelsDirName }/` ; } getUndownloadedChapters ( novelId ) { let onlineChapterList = this . _getNovelChapterList ( novelId ) . toPromise ( ) ; let offlineChapterList = this . getNovelChapterList ( novelId ) ; return new Promise ( ( resolve , reject ) => { Promise . all ( [ onlineChapterList , offlineChapterList ] ) . catch ( chapters => { let uniqueChapters = this . _getUniqueChapterList ( chapters ) ; resolve ( uniqueChapters ) ; } ) . then ( chapters => { let uniqueChapters = this . _getUniqueChapterList ( chapters ) ; resolve ( uniqueChapters ) ; } ) ; } ) ; } private _getUniqueChapterList ( chapters ) { let onlineChapters = chapters [ 0 ] ; let offlineChapters = chapters [ 1 ] ; console . log ( "online chapters" , onlineChapters ) ; console . log ( "offline chapters" , offlineChapters ) ; return _ . differenceBy ( onlineChapters , offlineChapters , "id" ) ; } private _getNovelChapterList ( id ) { console . log ( "DownloadService::_getNovelChapterList" ) ; return this . http . get ( `/api/novels/${ id }/chapters` ) . map ( ( response ) => { let data = < any > response . json ( ) || { } ; return data . map ( ( c ) : Chapter => new Chapter ( { id : c . id , number : c . number , title : c . title , content : "" } ) ) . sort ( ( a , b ) => b . number - a . number ) ; } ) . catch ( error => { return Observable . of ( [ ] ) ; } ) ; } addToQueue ( novel , chapters ) { let item = this . getItemFromQueue ( novel ) ; if ( item ) { item . chapters = item . chapters . concat ( chapters ) ; } else { item = new DownloadItem ( { novel : novel , chapters : chapters } ) ; console . log ( "adding item" , item ) ; this . queue . push ( item ) ; } this . download ( item ) ; } removeFromQueue ( item ) { _ . pull ( this . queue , item ) ; } getItemFromQueue ( novel ) { return _ . find ( this . queue , item => item . novel . id === novel . id ) ; } download ( downloadItem ) { let url = `/api/Novels/${ downloadItem . novel . id }/chapters/` ; this . createDir ( this . novelsDirName , true ) . then ( ( ) => { let novelDir = this . novelsDir + downloadItem . novel . id + "/" ; this . createDir ( downloadItem . novel . id ) . then ( entry => { this . retrieveChapters ( downloadItem , url , novelDir ) ; } ) . catch ( err => { console . log ( "error creating directory" , err ) ; this . removeFromQueue ( downloadItem ) ; } ) ; } ) . catch ( err => { console . log ( "error creating root directory" , err ) ; this . removeFromQueue ( downloadItem ) ; } ) ; } private retrieveChapters ( downloadItem , url , novelDir ) { _ . each ( downloadItem . chapters , chapter => { console . log ( "trying to download: " , chapter ) ; if ( chapter . status == DownloadStatus . Pending ) { this . downloadChapter ( chapter , url , novelDir ) ; } } ) ; } private downloadChapter ( chapter , url , novelDir ) { chapter . status = DownloadStatus . Ongoing ; this . fileTransfer . download ( `${ url }${ chapter . number }` , `${ novelDir }${ chapter . number }.json` ) . then ( entry => { console . log ( "download complete: " , entry . toURL ( ) ) ; chapter . status = DownloadStatus . Completed ; } ) . catch ( err => { console . log ( "error downloading, retrying..." , err ) ; this . downloadChapter ( chapter , url , novelDir ) ; } ) ; } private createDir ( novelId , isRoot = false ) { let rootDir = isRoot ? this . settingsService . settings . appStorageLocation : this . novelsDir ; return new Promise ( ( resolve , reject ) => { this . file . checkDir ( rootDir , novelId . toString ( ) ) . then ( ( ) => { resolve ( ) ; } ) . catch ( ( ) => { this . file . createDir ( rootDir , novelId . toString ( ) , false ) . then ( ( ) => { resolve ( ) ; } ) . catch ( err => { reject ( err ) ; } ) ; } ) ; } ) ; } getNovels ( ) { console . log ( "DownloadService::getNovels" ) ; return new Promise ( ( resolve ) => { return this . getNovelIdsFromFolders ( ) . then ( ids => { this . novelsLocalService . getNovels ( ids ) . then ( novels => { resolve ( novels ) ; } ) ; } ) . catch ( ( ) => { resolve ( [ ] ) ; } ) ; } ) ; } private getNovelIdsFromFolders ( ) { return new Promise ( ( resolve , reject ) => { this . file . listDir ( this . settingsService . settings . appStorageLocation , this . novelsDirName ) . then ( entries => { console . log ( "entries: " , entries ) ; let ids = _ . map ( entries , "name" ) ; ids = _ . map ( ids , id => parseInt ( id ) ) ; resolve ( ids ) ; } ) . catch ( ( ) => { resolve ( [ ] ) ; } ) ; } ) ; } getNovelChapterList ( id ) { console . log ( "DownloadService::getNovelChapterList" ) ; return new Promise ( ( resolve , reject ) => { let chapters = [ ] ; this . file . listDir ( this . novelsDir , id . toString ( ) ) . then ( entries => { let chapterNumbers = _ . map ( entries , "name" ) ; let unreadableChapters = 0 ; if ( chapterNumbers . length == 0 ) { resolve ( [ ] ) ; } _ . each ( chapterNumbers , chapterNumber => { this . readNovelChapter ( id , chapterNumber ) . then ( chapter => { chapters . push ( chapter ) ; if ( chapters . length >= chapterNumbers . length - unreadableChapters ) { resolve ( chapters ) ; } } ) . catch ( err => { unreadableChapters += 1 ; console . log ( "error reading chapter" , err ) ; if ( chapters . length >= chapterNumbers . length - unreadableChapters ) { resolve ( chapters ) ; } } ) ; } ) ; } ) . catch ( err => { console . log ( "no downloaded chapters for this one yet" , err ) ; resolve ( [ ] ) ; } ) ; } ) ; } readNovelChapter ( novelId , chapterNumber ) { let novelDir = `${ this . novelsDir }${ novelId }/` ; return new Promise ( ( resolve , reject ) => { this . file . readAsText ( novelDir , chapterNumber . toString ( ) ) . then ( value => { resolve ( JSON . parse ( value ) ) ; } ) . catch ( err => { reject ( err ) ; } ) ; } ) ; } getNovelChapterListFromQueue ( id ) { let item = _ . find ( this . queue , item => item . novel . id == id ) ; return item . chapters ; } async deleteNovels ( novels ) { return new Promise ( ( resolve , reject ) => { _ . each ( novels , async ( novel ) => { await this . file . removeRecursively ( this . novelsDir , novel . id . toString ( ) ) ; } ) ; resolve ( ) ; } ) ; } }