From f763fd66f5923457bf2ed87696e08d2fb6415ce0 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Sun, 10 Jul 2016 18:43:00 +0300 Subject: [PATCH] modal popup to download a book via a code --- app/pods/application/controller.js | 3 + app/pods/application/route.js | 5 + app/pods/application/template.hbs | 2 +- app/pods/book-manager/service.js | 272 ++++++++++++++++++ app/pods/book/adapter.js | 12 +- app/pods/components/add-book/component.js | 48 ++++ app/pods/components/add-book/template.hbs | 46 +++ .../components/bootstrap-modal/template.hbs | 2 +- app/pods/index/controller.js | 24 +- app/pods/index/template.hbs | 36 +-- app/pods/section/adapter.js | 18 +- config/environment.js | 7 +- 12 files changed, 441 insertions(+), 34 deletions(-) create mode 100644 app/pods/book-manager/service.js create mode 100644 app/pods/components/add-book/component.js create mode 100644 app/pods/components/add-book/template.hbs diff --git a/app/pods/application/controller.js b/app/pods/application/controller.js index e5f5088..262ff8b 100644 --- a/app/pods/application/controller.js +++ b/app/pods/application/controller.js @@ -17,6 +17,9 @@ export default Ember.Controller.extend({ return this.transitionToRoute('/'); } history.back(); + }, + refreshIndex(books) { + this.store.pushPayload('book', books); } } }); diff --git a/app/pods/application/route.js b/app/pods/application/route.js index b35789a..3fc3ba0 100644 --- a/app/pods/application/route.js +++ b/app/pods/application/route.js @@ -4,6 +4,11 @@ import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mi export default Ember.Route.extend(ApplicationRouteMixin, { session: Ember.inject.service('session'), nav: Ember.inject.service('nav'), + bookManager: Ember.inject.service(), + + beforeModel() { + return this.get('bookManager').updateIndex().then(this._super); + }, actions: { back() { diff --git a/app/pods/application/template.hbs b/app/pods/application/template.hbs index 31a99fe..97cbae6 100644 --- a/app/pods/application/template.hbs +++ b/app/pods/application/template.hbs @@ -3,4 +3,4 @@ {{liquid-outlet class="row"}} -{{bootstrap-modal content=modal}} +{{bootstrap-modal content=modal refreshIndex=(action 'refreshIndex')}} diff --git a/app/pods/book-manager/service.js b/app/pods/book-manager/service.js new file mode 100644 index 0000000..26edb17 --- /dev/null +++ b/app/pods/book-manager/service.js @@ -0,0 +1,272 @@ +/* global BackgroundTransfer */ +import Ember from 'ember'; +import ENV from 'funzo-app/config/environment'; + +export default Ember.Service.extend(Ember.Evented, { + downloadProgress: 0, + unzipProgress: 0, + + status: 'idle', + + init() { + if (window.cordova) { + this.set('baseDir', window.cordova.file.externalDataDirectory); + } else { + return; + // window.webkitRequestFileSystem(window.PERMENANT, 10 * 1024 * 1024 , (fs) => { console.log(fs); }, () => {}); + } + + this.set('contentDir', `${this.get('baseDir')}content`); + this.set('downloadDir', `${this.get('baseDir')}downloads`); + + this.set('bookDownloadDir', `${this.get('downloadDir')}/books`); + this.set('bookDir', `${this.get('contentDir')}/books`); + + this.set('tmpDir', `${this.get('baseDir')}tmp`); + + this.createDirectory(this.get('tmpDir')); + + this.createDirectory(this.get('contentDir')) + .then(this.createDirectory(this.get('bookDir'), this.get('contentDir'))); + + this.createDirectory(this.get('downloadDir')) + .then(this.createDirectory(this.get('bookDownloadDir'), this.get('downloadDir'))); + }, + + reset() { + this.setProperties({ + status: 'idle', + downloadProgress: 0, + unzipProgress: 0, + download: null + }); + }, + + addBook(code) { + this.set('status', 'downloading'); + return this.downloadBook(code) + .then(() => { + this.set('status', 'unzipping'); + return this.unzipBook(code); + }) + .then(() => this.updateIndex()) + .then(() => { + this.set('status', 'complete'); + }, (err) => { + this.set('status', 'error'); + this.set('error', err); + }); + }, + + updateIndex() { + return this.resolveFileURL(this.get('bookDir')) + .then((bookDir) => { + return this.readFolderContents(bookDir) + .then((bookFolders) => { + return Ember.RSVP.all(bookFolders.filterBy('isDirectory', true).map((bookFolder) => { + return this.resolveFileURL(`${this.get('bookDir')}/${bookFolder.name}/book.json`) + .then((file) => this.readFileContents(file)); + })); + }).then((bookMetas) => { + return Ember.RSVP.resolve(`[${bookMetas.join(',')}]`); + }).then((indexContent) => { + return this.createFile(bookDir, 'index.json') + .then((indexFile) => this.writeFileContents(indexFile, indexContent)) + .then((indexContent) => { + let json = JSON.parse(indexContent); + this.trigger('booksUpdated', json); + return Ember.RSVP.resolve(json); + }); + }); + }); + }, + + /** + * Get the url from a code + * + * @param code {String} + * @return {String} the url + **/ + + urlForBook(code) { + if (code === 'demo' || code === 'demo-ndl') { + return 'https://www.dropbox.com/s/tbfvbbotoaxp4m5/demo.zip?dl=1'; + } + return `${ENV.APP.bookURLBase}${code}`; + }, + + /** + * Start download of a book + * + * @param code {String} + * @return {Promise} + **/ + + downloadBook(code) { + let url = this.urlForBook(code); + if (code === 'demo-ndl') { + return Ember.RSVP.resolve(); + } + return this.fileTransfer(url, `${this.get('bookDownloadDir')}/${code}.zip`); + }, + + /** + * Unzip the book + * unzip to a tmp location + * read permalink + * move to books/permalink + **/ + unzipBook(code) { + let zipPath = `${this.get('bookDownloadDir')}/${code}.zip`; + let unzipDest = `${this.get('tmpDir')}/${code}`; + + return this.createDirectory(code, this.get('tmpDir')) + .then(() => this.unzip(zipPath, unzipDest)) + .then(() => this.resolveFileURL(unzipDest + '/book.json')) + .then((file) => this.readFileContents(file)) + .then((bookJSON) => { + let bookMeta = JSON.parse(bookJSON); + let permalink = bookMeta.permalink; + + return Ember.RSVP.hash({ + destFolder: this.resolveFileURL(this.get('bookDir')), + unzipFolder: this.resolveFileURL(unzipDest) + }).then((res) => { + return this.moveFile(res.unzipFolder, res.destFolder, permalink); + }); + }); + }, + + createDirectory(folder, dest = '') { + dest = (dest ? `${dest}` : this.get('baseDir')); + + return new Ember.RSVP.Promise((resolve, reject) => { + window.resolveLocalFileSystemURL(dest, function(dir) { + dir.getDirectory(folder, { create: true }, function(file) { + if (file) { + return resolve(file); + } + return reject(); + }); + }); + }); + }, + + fileTransfer(uri, destPath) { + return new Ember.RSVP.Promise((res, rej) => { + let fileTransfer = new window.FileTransfer(); + uri = encodeURI(uri); + + fileTransfer.onprogress = (e) => { + if (e.lengthComputable) { + this.set('downloadProgress', Math.ceil(100 * e.loaded / e.total)); + } else { + this.incrementProperty('downloadProgress'); + } + }; + + fileTransfer.download(uri, destPath, res, rej, false); + this.set('download', fileTransfer); + + }); + // let downloader = new BackgroundTransfer.BackgroundDownloader(); + // let onprogress = (e) => { + // this.set('downloadProgress', Math.ceil(100 * e.bytesReceived / e.totalBytesToReceive)); + // }; + + // return this.resolveFileURL(destDir) + // .then(dir => this.createFile(dir, destFileName)) + // .then(file => { + // let download = downloader.createDownload(uri, file); + // this.set('download', download); + // return new Ember.RSVP.Promise((res, rej) => { + // download.startAsync().then(res, rej, onprogress); + // }); + // }); + }, + + unzip(source, dest) { + return new Ember.RSVP.Promise((resolve, reject) => window.zip.unzip(source, dest, (result) => (result < 0) ? reject() : resolve(), (e) => { + if (e.lengthComputable) { + this.set('zipProgress', Math.ceil(100 * e.loaded / e.total)); + } else { + this.incrementProperty('zipProgress'); + } + })); + }, + + resolveFileURL(url) { + return new Ember.RSVP.Promise((res, rej) => { + window.resolveLocalFileSystemURL(url, (file) => res(file), rej); + }); + }, + + createFile(dir, file) { + return new Ember.RSVP.Promise((resolve) => { + dir.getFile(file, { create: true }, resolve); + }); + }, + + getFile(dir, file) { + return new Ember.RSVP.Promise((resolve) => { + dir.getFile(file, resolve); + }); + }, + + moveFile(file, dest, newName) { + return new Ember.RSVP.Promise((res) => { + file.moveTo(dest, newName); + res(); + }); + }, + + readFolderContents(dir) { + return new Ember.RSVP.Promise(function(res, rej) { + let reader = dir.createReader(); + let entries = []; + let readEntries = function() { + reader.readEntries(function(results) { + if (!results.length) { + res(entries.sort()); + } else { + entries = entries.concat(Array.prototype.slice.call(results || [], 0)); + readEntries(); + } + }, rej); + }; + readEntries(); + }); + }, + + readFileContents(file) { + return new Ember.RSVP.Promise(function(res, rej) { + file.file((file) => { + let reader = new FileReader(); + + reader.onload = (e) => { + res(e.target.result); + }; + + reader.readAsText(file); + }, rej); + }); + }, + + writeFileContents(file, content) { + return new Ember.RSVP.Promise((res, rej) => { + file.createWriter((fileWriter) => { + let blob = new Blob([content], { type: 'text/plain' }); + + fileWriter.onwriteend = () => { + res(content); + }; + + file.onerror = (e) => { + rej(e.toString()); + }; + + fileWriter.write(blob); + }); + }); + } +}); diff --git a/app/pods/book/adapter.js b/app/pods/book/adapter.js index d261bf9..2b706d6 100644 --- a/app/pods/book/adapter.js +++ b/app/pods/book/adapter.js @@ -2,11 +2,19 @@ import DS from 'ember-data'; export default DS.RESTAdapter.extend({ urlForFindAll() { - return 'content/books/index.json'; + let url = 'content/books/index.json'; + if (window.cordova) { + url = window.cordova.file.externalDataDirectory + url; + } + return url; }, urlForFindRecord(permalink) { - return `content/books/${permalink}/book.json`; + let url = `content/books/${permalink}/book.json`; + if (window.cordova) { + url = window.cordova.file.externalDataDirectory + url; + } + return url; }, urlForFindHasMany(id, modelName, snapshot) { diff --git a/app/pods/components/add-book/component.js b/app/pods/components/add-book/component.js new file mode 100644 index 0000000..00eba0d --- /dev/null +++ b/app/pods/components/add-book/component.js @@ -0,0 +1,48 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + bookManager: Ember.inject.service(), + + classNames: ['modal-dialog'], + downloadProgress: Ember.computed.alias('bookManager.downloadProgress'), + zipProgress: Ember.computed.alias('bookManager.zipProgress'), + status: Ember.computed.alias('bookManager.status'), + + isIdle: Ember.computed.equal('status', 'idle'), + isDownloading: Ember.computed.equal('status', 'downloading'), + isUnzipping: Ember.computed.equal('status', 'unzipping'), + isComplete: Ember.computed.equal('status', 'complete'), + isError: Ember.computed.equal('status', 'error'), + + title: 'Add A Book', + + code: 'demo', + + didInsertElement() { + this.get('bookManager').reset(); + }, + + actions: { + submit() { + if (!window.cordova) { + return alert('only works in app'); + } + + this.get('bookManager').addBook(this.get('code')) + .then(() => { + Ember.$('.modal').modal('hide'); + }); + // }, (error) => { + // console.log('download error source ' + error.source); + // console.log('download error target ' + error.target); + // console.log('upload error code' + error.code); + // }); + }, + cancel() { + if (this.get('bookManager.download') && this.get('isDownloading')) { + this.get('bookManager.download').abort(); + } + this.get('bookManager').reset(); + } + } +}); diff --git a/app/pods/components/add-book/template.hbs b/app/pods/components/add-book/template.hbs new file mode 100644 index 0000000..6541272 --- /dev/null +++ b/app/pods/components/add-book/template.hbs @@ -0,0 +1,46 @@ + diff --git a/app/pods/components/bootstrap-modal/template.hbs b/app/pods/components/bootstrap-modal/template.hbs index 50626f8..004248b 100644 --- a/app/pods/components/bootstrap-modal/template.hbs +++ b/app/pods/components/bootstrap-modal/template.hbs @@ -1 +1 @@ -{{component content.component args=content.args}} +{{component content.component args=content.args refreshIndex=(action refreshIndex)}} diff --git a/app/pods/index/controller.js b/app/pods/index/controller.js index d40cc65..940704e 100644 --- a/app/pods/index/controller.js +++ b/app/pods/index/controller.js @@ -2,5 +2,27 @@ import Ember from 'ember'; import ENV from 'funzo-app/config/environment'; export default Ember.Controller.extend({ - bookOnlyMode: ENV.APP.bookOnlyMode + bookManager: Ember.inject.service(), + + bookOnlyMode: ENV.APP.bookOnlyMode, + modal: Ember.inject.service('bootstrap-modal'), + bookDir: Ember.computed('bookManager.bookDir', function() { + return this.get('bookManager.bookDir'); + }), + + init() { + this.get('bookManager').on('booksUpdated', (books) => { + this.store.pushPayload('book', { books }); + this.set('model.books', this.store.peekAll('book')); + }); + return this._super(); + }, + + actions: { + addBook() { + this.set('modal.component', 'add-book'); + + Ember.$('.modal').modal('show'); + } + } }); \ No newline at end of file diff --git a/app/pods/index/template.hbs b/app/pods/index/template.hbs index 2f84de9..02a29a2 100644 --- a/app/pods/index/template.hbs +++ b/app/pods/index/template.hbs @@ -1,11 +1,10 @@ -{{#if bookOnlyMode}}
{{#each model.books as |book|}}
{{#link-to 'book' book}}
- +
{{book.title}}
{{book.author}}
@@ -13,27 +12,20 @@
{{/link-to}}
- {{/each}} -
-
-{{else}} -
-
- {{#each model.courses as |course|}} -
-
-
- {{course.title}} -
-
-

Subject: {{course.subject}}

-
- + {{!--
+ library_add + +
--}} {{/each}}
-
-{{/if}} \ No newline at end of file +
\ No newline at end of file diff --git a/app/pods/section/adapter.js b/app/pods/section/adapter.js index c83d395..8521394 100644 --- a/app/pods/section/adapter.js +++ b/app/pods/section/adapter.js @@ -2,14 +2,26 @@ import DS from 'ember-data'; export default DS.RESTAdapter.extend({ urlForFindRecord(permalink, modelName, snapshot) { - return `content/books/${snapshot.record.get('book.id')}/${permalink}.json`; + let url = `content/books/${snapshot.record.get('book.id')}/${permalink}.json`; + if (window.cordova) { + url = window.cordova.file.externalDataDirectory + url; + } + return url; }, urlForQuery(query) { - return `content/books/${query.book_id}/index.json`; + let url = `content/books/${query.book_id}/index.json`; + if (window.cordova) { + url = window.cordova.file.externalDataDirectory + url; + } + return url; }, urlForQueryRecord(query) { - return `content/books/${query.book_id}/${query.section_id}.json`; + let url = `content/books/${query.book_id}/${query.section_id}.json`; + if (window.cordova) { + url = window.cordova.file.externalDataDirectory + url; + } + return url; } }); \ No newline at end of file diff --git a/config/environment.js b/config/environment.js index b28071a..4ac1876 100644 --- a/config/environment.js +++ b/config/environment.js @@ -5,8 +5,8 @@ var ifaces = os.networkInterfaces(); var addresses = []; for (var dev in ifaces) { - ifaces[dev].forEach(function(details){ - if(details.family === 'IPv4' && details.address !== '127.0.0.1') { + ifaces[dev].forEach(function(details) { + if (details.family === 'IPv4' && details.address !== '127.0.0.1') { addresses.push(details.address); } }); @@ -41,7 +41,7 @@ module.exports = function(environment) { platform: 'android' } }, - + emberPouch: { localDb: 'funzo' }, @@ -81,7 +81,6 @@ module.exports = function(environment) { ENV.staging = true; } - if (environment === 'production') { ENV.apiUrl = 'http://funzo-app.herokuapp.com/api/v1'; ENV.production = true;