Showing
31 changed files
with
741 additions
and
361 deletions
| ... | @@ -4,21 +4,64 @@ Allows downloading of files referenced by a http/https URI inside a couchdb/pouc | ... | @@ -4,21 +4,64 @@ Allows downloading of files referenced by a http/https URI inside a couchdb/pouc |
| 4 | 4 | ||
| 5 | # Usage | 5 | # Usage |
| 6 | 6 | ||
| 7 | -### In cordova projects | 7 | +## Use it with pouchdb |
| 8 | 8 | ||
| 9 | -### In electron projects | 9 | +```typescript |
| 10 | +import {ServiceLocator} from 'bsync-client/dist/browser-build'; | ||
| 10 | 11 | ||
| 11 | -Within the main process bsync needs to be integrated an initiated. | 12 | +ServiceLocator.getConfig().setConfig('itemKey', 'type'); |
| 13 | +ServiceLocator.getConfig().setConfig('itemValue', 'asset'); | ||
| 12 | 14 | ||
| 13 | - import {Bsync} from 'bsync'; | 15 | +let localDb = new PouchDB('local-pouch-db'); |
| 14 | - Bsync.init(ipcMain, filePath); | 16 | +let fileReplicator = ServiceLocator.getFileReplicator(); |
| 15 | 17 | ||
| 16 | -# Testing (Browser) | 18 | +localDb.put({ |
| 19 | + _id : "_design/index_type", | ||
| 20 | + views : { | ||
| 21 | + type : { | ||
| 22 | + map : function(doc) { | ||
| 23 | + if (doc[fileReplicator.itemKey]) { emit(doc[fileReplicator.itemKey]); } | ||
| 24 | + }.toString() | ||
| 25 | + } | ||
| 26 | + } | ||
| 27 | +}); | ||
| 28 | + | ||
| 29 | +fileReplicator.once('complete', () => { | ||
| 30 | + // All file are downloaded | ||
| 31 | +}); | ||
| 32 | + | ||
| 33 | +db.query('index_type/type',{ | ||
| 34 | + include_docs : true, | ||
| 35 | + key : fileReplicator.itemValue | ||
| 36 | +}).then((res) => { | ||
| 37 | + let docs = { docs : [] }; | ||
| 38 | + for (let r of res.rows) { | ||
| 39 | + docs.docs.push(r.doc); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + fileReplicator.pushChanges(docs); | ||
| 43 | + fileReplicator.start(); | ||
| 44 | +}); | ||
| 45 | +``` | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +## In electron projects | ||
| 49 | + | ||
| 50 | +Within the `main process` bsync needs to be integrated an initiated. | ||
| 51 | + | ||
| 52 | +```typescript | ||
| 53 | +import {Bsync} from 'bsync'; | ||
| 54 | +Bsync.init(ipcMain, filePath); | ||
| 55 | +``` | ||
| 56 | + | ||
| 57 | +# Testing (Browser/Node) | ||
| 17 | 58 | ||
| 18 | `npm test` | 59 | `npm test` |
| 19 | 60 | ||
| 20 | # Testing (Cordova) | 61 | # Testing (Cordova) |
| 21 | 62 | ||
| 63 | +`npm run test:cordova` | ||
| 64 | + | ||
| 22 | 65 | ||
| 23 | 66 | ||
| 24 | 67 | ... | ... |
| ... | @@ -9,13 +9,13 @@ export default { | ... | @@ -9,13 +9,13 @@ export default { |
| 9 | entry: './src/browser-main.ts', | 9 | entry: './src/browser-main.ts', |
| 10 | dest: './dist/browser-build.js', | 10 | dest: './dist/browser-build.js', |
| 11 | 11 | ||
| 12 | - format: 'cjs', | 12 | + format: 'umd', |
| 13 | 13 | ||
| 14 | sourceMap: true , | 14 | sourceMap: true , |
| 15 | 15 | ||
| 16 | plugins: [ | 16 | plugins: [ |
| 17 | typescript(), | 17 | typescript(), |
| 18 | - // globals(), | 18 | + globals(), |
| 19 | - // builtins() | 19 | + builtins() |
| 20 | ] | 20 | ] |
| 21 | }; | 21 | }; |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | import typescript from 'rollup-plugin-typescript'; | 1 | import typescript from 'rollup-plugin-typescript'; |
| 2 | +import builtins from 'rollup-plugin-node-builtins'; | ||
| 3 | +import globals from 'rollup-plugin-node-globals'; | ||
| 2 | 4 | ||
| 3 | export default { | 5 | export default { |
| 4 | 6 | ||
| ... | @@ -10,6 +12,8 @@ export default { | ... | @@ -10,6 +12,8 @@ export default { |
| 10 | format: 'cjs', | 12 | format: 'cjs', |
| 11 | 13 | ||
| 12 | plugins: [ | 14 | plugins: [ |
| 13 | - typescript() | 15 | + typescript(), |
| 16 | + globals(), | ||
| 17 | + builtins() | ||
| 14 | ] | 18 | ] |
| 15 | }; | 19 | }; |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
| 1 | 'use strict'; | 1 | 'use strict'; |
| 2 | 2 | ||
| 3 | -var rxjs_Observable = require('rxjs/Observable'); | 3 | +var events = require('events'); |
| 4 | var http = require('http'); | 4 | var http = require('http'); |
| 5 | var https = require('https'); | 5 | var https = require('https'); |
| 6 | var fs = require('fs'); | 6 | var fs = require('fs'); |
| 7 | 7 | ||
| 8 | -var NodeFileHandler = (function () { | 8 | +function __extends(d, b) { |
| 9 | + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; | ||
| 10 | + function __() { this.constructor = d; } | ||
| 11 | + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +var Util = (function () { | ||
| 15 | + function Util() { | ||
| 16 | + } | ||
| 17 | + Util.getNameHash = function (path) { | ||
| 18 | + for (var r = 0, i = 0; i < path.length; i++) { | ||
| 19 | + r = (r << 5) - r + path.charCodeAt(i), r &= r; | ||
| 20 | + } | ||
| 21 | + return "bsync_" + Math.abs(r); | ||
| 22 | + }; | ||
| 23 | + /** | ||
| 24 | + * index >= 0, localFile is in files | ||
| 25 | + * index < 0, localFile is not in files | ||
| 26 | + */ | ||
| 27 | + Util.getFileIndex = function (files, localFile) { | ||
| 28 | + for (var i = 0; i < files.length; i++) { | ||
| 29 | + if (localFile == files[i].target) { | ||
| 30 | + return i; | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + return -1; | ||
| 34 | + }; | ||
| 35 | + Util.getFilesForCleanup = function (files, localFiles) { | ||
| 36 | + var filesForCleanup = []; | ||
| 37 | + for (var _i = 0, localFiles_1 = localFiles; _i < localFiles_1.length; _i++) { | ||
| 38 | + var localFile = localFiles_1[_i]; | ||
| 39 | + var index = -1; | ||
| 40 | + index = Util.getFileIndex(files, localFile); | ||
| 41 | + if (index < 0) { | ||
| 42 | + filesForCleanup.push(localFile); | ||
| 43 | + } | ||
| 44 | + else { | ||
| 45 | + // splice for performance improvement only | ||
| 46 | + files.splice(index, 1); | ||
| 47 | + } | ||
| 48 | + } | ||
| 49 | + return filesForCleanup; | ||
| 50 | + }; | ||
| 51 | + return Util; | ||
| 52 | +}()); | ||
| 53 | + | ||
| 54 | +var NodeFileHandler = (function (_super) { | ||
| 55 | + __extends(NodeFileHandler, _super); | ||
| 9 | function NodeFileHandler() { | 56 | function NodeFileHandler() { |
| 57 | + _super.apply(this, arguments); | ||
| 10 | } | 58 | } |
| 11 | NodeFileHandler.prototype.selectProtocol = function (url) { | 59 | NodeFileHandler.prototype.selectProtocol = function (url) { |
| 12 | if (url.search(/^http:\/\//) === 0) { | 60 | if (url.search(/^http:\/\//) === 0) { |
| ... | @@ -20,18 +68,19 @@ var NodeFileHandler = (function () { | ... | @@ -20,18 +68,19 @@ var NodeFileHandler = (function () { |
| 20 | } | 68 | } |
| 21 | }; | 69 | }; |
| 22 | NodeFileHandler.prototype.download = function (source, target) { | 70 | NodeFileHandler.prototype.download = function (source, target) { |
| 71 | + var _this = this; | ||
| 23 | var handler = this.selectProtocol(source); | 72 | var handler = this.selectProtocol(source); |
| 24 | - return rxjs_Observable.Observable.create(function (subscriber) { | 73 | + if (!handler) { |
| 25 | - if (!handler) { | 74 | + this.emit("error", "No handler for source: " + source); |
| 26 | - subscriber.error("No handler for source: " + source); | 75 | + return this; |
| 27 | - return; | 76 | + } |
| 28 | - } | 77 | + // file already exists and is not empty |
| 29 | - // file already exists and is not empty | 78 | + if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) { |
| 30 | - if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) { | 79 | + this.emit("complete"); |
| 31 | - subscriber.complete(); | 80 | + return this; |
| 32 | - return; | 81 | + } |
| 33 | - } | 82 | + else { |
| 34 | - var file = fs.createWriteStream(target, { 'flags': 'a' }); | 83 | + var file_1 = fs.createWriteStream(target, { 'flags': 'a' }); |
| 35 | handler.get(source, function (response) { | 84 | handler.get(source, function (response) { |
| 36 | var size = response.headers['content-length']; // in bytes | 85 | var size = response.headers['content-length']; // in bytes |
| 37 | var prog = 0; // already downloaded | 86 | var prog = 0; // already downloaded |
| ... | @@ -39,33 +88,53 @@ var NodeFileHandler = (function () { | ... | @@ -39,33 +88,53 @@ var NodeFileHandler = (function () { |
| 39 | var nextProg = (1 / progCounts); | 88 | var nextProg = (1 / progCounts); |
| 40 | response.on('data', function (chunk) { | 89 | response.on('data', function (chunk) { |
| 41 | prog += chunk.length; | 90 | prog += chunk.length; |
| 42 | - file.write(chunk, 'binary'); | 91 | + file_1.write(chunk, 'binary'); |
| 43 | if ((prog / size) > nextProg) { | 92 | if ((prog / size) > nextProg) { |
| 44 | - subscriber.next(prog / size); | 93 | + _this.emit('progress', prog / size); |
| 45 | nextProg += (1 / progCounts); | 94 | nextProg += (1 / progCounts); |
| 46 | } | 95 | } |
| 47 | }); | 96 | }); |
| 48 | - response.on('end', function () { | 97 | + response.once('end', function () { |
| 49 | - file.end(); | 98 | + file_1.end(); |
| 50 | - subscriber.complete(); | 99 | + _this.emit('complete'); |
| 51 | }); | 100 | }); |
| 52 | }).on('error', function (error) { | 101 | }).on('error', function (error) { |
| 53 | fs.unlink(target); | 102 | fs.unlink(target); |
| 54 | - subscriber.error("Error while downloading: " + error); | 103 | + _this.emit("error", "Error while downloading: " + error); |
| 55 | }); | 104 | }); |
| 56 | - }); | 105 | + return this; |
| 106 | + } | ||
| 107 | + }; | ||
| 108 | + NodeFileHandler.prototype.cleanup = function (files, basePath) { | ||
| 109 | + try { | ||
| 110 | + var localFiles = fs.readdirSync(basePath); | ||
| 111 | + Util.getFilesForCleanup(files, localFiles) | ||
| 112 | + .forEach(function (file) { | ||
| 113 | + fs.unlinkSync(basePath + "/" + file); | ||
| 114 | + }); | ||
| 115 | + } | ||
| 116 | + catch (e) { | ||
| 117 | + } | ||
| 118 | + return this; | ||
| 57 | }; | 119 | }; |
| 58 | return NodeFileHandler; | 120 | return NodeFileHandler; |
| 59 | -}()); | 121 | +}(events.EventEmitter)); |
| 60 | 122 | ||
| 61 | var Bsync = (function () { | 123 | var Bsync = (function () { |
| 62 | function Bsync() { | 124 | function Bsync() { |
| 63 | } | 125 | } |
| 64 | - Bsync.configIpcMain = function (ipcMain, downloadDir) { | 126 | + Bsync.configIpcMain = function (ipcMain, basePath) { |
| 65 | - var nodeFileHander = new NodeFileHandler(); | ||
| 66 | ipcMain.on('bsync-download', function (event, args) { | 127 | ipcMain.on('bsync-download', function (event, args) { |
| 67 | - nodeFileHander.download(args.source, downloadDir + args.target) | 128 | + var nodeFileHander = new NodeFileHandler(); |
| 68 | - .subscribe(function (progress) { event.sender.send('bsync-download-progress', progress); }, function (error) { event.sender.send('bsync-download-error', error); }, function () { event.sender.send('bsync-download-complete'); }); | 129 | + nodeFileHander.download(args.source, basePath + "/" + args.target) |
| 130 | + .on('progress', function (progress) { event.sender.send('bsync-download-progress', progress); }) | ||
| 131 | + .once('error', function (error) { nodeFileHander.removeAllListeners(); event.sender.send('bsync-download-error', error); }) | ||
| 132 | + .once('complete', function () { nodeFileHander.removeAllListeners(); event.sender.send('bsync-download-complete'); }); | ||
| 133 | + }); | ||
| 134 | + ipcMain.on('bsync-cleanup', function (event, args) { | ||
| 135 | + var nodeFileHandler = new NodeFileHandler(); | ||
| 136 | + nodeFileHandler.cleanup(args.files, basePath); | ||
| 137 | + event.sender.send('bsync-cleanup-complete'); | ||
| 69 | }); | 138 | }); |
| 70 | }; | 139 | }; |
| 71 | return Bsync; | 140 | return Bsync; | ... | ... |
This diff is collapsed. Click to expand it.
index.d.ts
0 → 100644
| 1 | + | ||
| 2 | +interface FileReplicator { | ||
| 3 | + start(retries?:number) : void; | ||
| 4 | + clear() : void; | ||
| 5 | + cleanup() : void; | ||
| 6 | + pushChanges(change: any) : void; | ||
| 7 | + on(event:string, handler:(...params: any[]) => void) : FileReplicator; | ||
| 8 | + once(event:string, handler:(...params: any[]) => void) : FileReplicator; | ||
| 9 | + removeAllListeners() : FileReplicator; | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +declare var bsyncClient: { | ||
| 13 | + | ||
| 14 | + CONFIG_TARGET_DIRECTORY : string ; | ||
| 15 | + | ||
| 16 | + ServiceLocator : { | ||
| 17 | + | ||
| 18 | + getConfig : () => { | ||
| 19 | + getConfig : (key:string) => any; | ||
| 20 | + setConfig : (key:string, value:any) => void; | ||
| 21 | + hasConfig : (key:string) => boolean; | ||
| 22 | + }; | ||
| 23 | + | ||
| 24 | + getFileReplicator : () => FileReplicator; | ||
| 25 | + | ||
| 26 | + } | ||
| 27 | +}; | ||
| 28 | + | ||
| 29 | +declare module "bsync-client" { | ||
| 30 | + export = bsyncClient; | ||
| 31 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -3,6 +3,7 @@ | ... | @@ -3,6 +3,7 @@ |
| 3 | "version": "1.0.0", | 3 | "version": "1.0.0", |
| 4 | "description": "", | 4 | "description": "", |
| 5 | "main": "dist/browser-build.js", | 5 | "main": "dist/browser-build.js", |
| 6 | + "typings": "index.d.ts", | ||
| 6 | "scripts": { | 7 | "scripts": { |
| 7 | "build": "npm run build:node && npm run build:browser", | 8 | "build": "npm run build:node && npm run build:browser", |
| 8 | "build:node": "rollup -c ./config/rollup.config.node.js", | 9 | "build:node": "rollup -c ./config/rollup.config.node.js", |
| ... | @@ -16,9 +17,6 @@ | ... | @@ -16,9 +17,6 @@ |
| 16 | }, | 17 | }, |
| 17 | "author": "", | 18 | "author": "", |
| 18 | "license": "ISC", | 19 | "license": "ISC", |
| 19 | - "dependencies": { | ||
| 20 | - "rxjs": "^5.0.2" | ||
| 21 | - }, | ||
| 22 | "devDependencies": { | 20 | "devDependencies": { |
| 23 | "@types/jasmine": "^2.5.40", | 21 | "@types/jasmine": "^2.5.40", |
| 24 | "cordova": "^6.4.0", | 22 | "cordova": "^6.4.0", | ... | ... |
| 1 | #!/bin/bash | 1 | #!/bin/bash |
| 2 | 2 | ||
| 3 | curl -X DELETE http://admin:admin@127.0.0.1:5984/pouch_test_db | 3 | curl -X DELETE http://admin:admin@127.0.0.1:5984/pouch_test_db |
| 4 | -rm -R ./.tmp | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 4 | +rm -rf ./.tmp | ||
| 5 | +rm -rf ./.tmp-files | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | #!/bin/bash | 1 | #!/bin/bash |
| 2 | - | 2 | +rm -rf ./.tmp |
| 3 | +rm -rf ./.tmp-files | ||
| 3 | curl -X DELETE http://admin:admin@127.0.0.1:5984/pouch_test_db | 4 | curl -X DELETE http://admin:admin@127.0.0.1:5984/pouch_test_db |
| 4 | curl -X PUT http://admin:admin@127.0.0.1:5984/pouch_test_db | 5 | curl -X PUT http://admin:admin@127.0.0.1:5984/pouch_test_db | ... | ... |
| ... | @@ -7,15 +7,14 @@ echo "cordova $COMMAND $PLATFORM" | ... | @@ -7,15 +7,14 @@ echo "cordova $COMMAND $PLATFORM" |
| 7 | 7 | ||
| 8 | rollup --config ./config/rollup.config.cordova-test.js | 8 | rollup --config ./config/rollup.config.cordova-test.js |
| 9 | cp ./spec/cordova-plugin-test/plugin.xml .tmp/plugin.xml | 9 | cp ./spec/cordova-plugin-test/plugin.xml .tmp/plugin.xml |
| 10 | -cp ./node_modules/rxjs/bundles/Rx.min.js .tmp/Rx.min.js | ||
| 11 | 10 | ||
| 12 | cd .. | 11 | cd .. |
| 13 | -rm -r ./bsync-client-test-app | 12 | +rm -rf ./bsync-client-test-app |
| 14 | ./bsync-client/node_modules/.bin/cordova create bsync-client-test-app | 13 | ./bsync-client/node_modules/.bin/cordova create bsync-client-test-app |
| 15 | cd ./bsync-client-test-app | 14 | cd ./bsync-client-test-app |
| 16 | 15 | ||
| 17 | ../bsync-client/node_modules/.bin/cordova platform add $PLATFORM | 16 | ../bsync-client/node_modules/.bin/cordova platform add $PLATFORM |
| 18 | -../bsync-client/node_modules/.bin/cordova plugin add ../bsync-client | 17 | +# ../bsync-client/node_modules/.bin/cordova plugin add ../bsync-client |
| 19 | ../bsync-client/node_modules/.bin/cordova plugin add ../bsync-client/.tmp | 18 | ../bsync-client/node_modules/.bin/cordova plugin add ../bsync-client/.tmp |
| 20 | ../bsync-client/node_modules/.bin/cordova plugin add cordova-plugin-test-framework | 19 | ../bsync-client/node_modules/.bin/cordova plugin add cordova-plugin-test-framework |
| 21 | 20 | ||
| ... | @@ -27,4 +26,4 @@ else | ... | @@ -27,4 +26,4 @@ else |
| 27 | cordova emulate $PLATFORM | 26 | cordova emulate $PLATFORM |
| 28 | fi | 27 | fi |
| 29 | 28 | ||
| 30 | -rm -R ../bsync-client/.tmp | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 29 | +rm -rf ../bsync-client/.tmp | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -5,8 +5,6 @@ import {TestFileHandler} from './file-handler/test-file-handler'; | ... | @@ -5,8 +5,6 @@ import {TestFileHandler} from './file-handler/test-file-handler'; |
| 5 | 5 | ||
| 6 | ServiceLocator.addFileHandler(ENV_UNKNOWN, new TestFileHandler()); | 6 | ServiceLocator.addFileHandler(ENV_UNKNOWN, new TestFileHandler()); |
| 7 | 7 | ||
| 8 | -import '../src/browser-main'; | ||
| 9 | - | ||
| 10 | declare var emit:any; | 8 | declare var emit:any; |
| 11 | 9 | ||
| 12 | const dbUrl = 'http://admin:admin@localhost:5984/pouch_test_db'; | 10 | const dbUrl = 'http://admin:admin@localhost:5984/pouch_test_db'; |
| ... | @@ -49,32 +47,71 @@ describe("Integration tests with couchdb", () => { | ... | @@ -49,32 +47,71 @@ describe("Integration tests with couchdb", () => { |
| 49 | 47 | ||
| 50 | it("Should successfully download several files", (done) => { | 48 | it("Should successfully download several files", (done) => { |
| 51 | TestFileHandler.setErrorRate(0); | 49 | TestFileHandler.setErrorRate(0); |
| 52 | - ServiceLocator.getFileReplicator().init(); | 50 | + let replicator = ServiceLocator.getFileReplicator(); |
| 53 | let index = 0; | 51 | let index = 0; |
| 54 | 52 | ||
| 55 | - localDb.replicate.from(dbUrl) | 53 | + replicator.clear(); |
| 56 | - .on('file-replicator-complete', event => { | 54 | + |
| 55 | + replicator | ||
| 56 | + .on('file-complete', () => { | ||
| 57 | index++; | 57 | index++; |
| 58 | }) | 58 | }) |
| 59 | - .on('complete', () => { | 59 | + .once('complete', () => { |
| 60 | expect(index).toEqual(3); | 60 | expect(index).toEqual(3); |
| 61 | - done(); | 61 | + done(); |
| 62 | + }); | ||
| 63 | + | ||
| 64 | + localDb.replicate.from(dbUrl) | ||
| 65 | + .once('complete', () => { | ||
| 66 | + | ||
| 67 | + localDb.query('index_type/type',{ | ||
| 68 | + include_docs : true, | ||
| 69 | + key : replicator.itemValue | ||
| 70 | + }).then((res) => { | ||
| 71 | + let docs = []; | ||
| 72 | + for (let r of res.rows) { | ||
| 73 | + docs.push(r.doc); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + replicator.pushChanges(docs); | ||
| 77 | + replicator.start(); | ||
| 78 | + }); | ||
| 62 | }); | 79 | }); |
| 63 | }); | 80 | }); |
| 64 | 81 | ||
| 65 | it("Should trigger errors, but successfully download with retries", (done) => { | 82 | it("Should trigger errors, but successfully download with retries", (done) => { |
| 66 | TestFileHandler.setErrorRate(0.8); | 83 | TestFileHandler.setErrorRate(0.8); |
| 67 | - ServiceLocator.getFileReplicator().init(); | 84 | + let replicator = ServiceLocator.getFileReplicator(); |
| 68 | let errors = 0; | 85 | let errors = 0; |
| 69 | 86 | ||
| 70 | - localDb.replicate.from(dbUrl) | 87 | + replicator.clear(); |
| 71 | - .on('file-replicator-error', event => { | 88 | + |
| 89 | + replicator | ||
| 90 | + .on('file-error', () => { | ||
| 72 | errors++; | 91 | errors++; |
| 73 | }) | 92 | }) |
| 74 | - .on('complete', () => { | 93 | + .once('complete', () => { |
| 75 | expect(errors).toBeGreaterThanOrEqual(1); | 94 | expect(errors).toBeGreaterThanOrEqual(1); |
| 76 | - done(); | 95 | + done(); |
| 96 | + }); | ||
| 97 | + | ||
| 98 | + localDb.replicate.from(dbUrl) | ||
| 99 | + .once('complete', () => { | ||
| 100 | + | ||
| 101 | + localDb.query('index_type/type',{ | ||
| 102 | + include_docs : true, | ||
| 103 | + key : replicator.itemValue | ||
| 104 | + }).then((res) => { | ||
| 105 | + let docs = []; | ||
| 106 | + for (let r of res.rows) { | ||
| 107 | + docs.push(r.doc); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + replicator.pushChanges(docs); | ||
| 111 | + replicator.start(); | ||
| 112 | + }); | ||
| 77 | }); | 113 | }); |
| 114 | + | ||
| 78 | }); | 115 | }); |
| 79 | 116 | ||
| 80 | }); | 117 | }); | ... | ... |
| ... | @@ -5,14 +5,11 @@ | ... | @@ -5,14 +5,11 @@ |
| 5 | version="1.0.0" | 5 | version="1.0.0" |
| 6 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | 6 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
| 7 | 7 | ||
| 8 | - <name>bsync cordova test</name> | 8 | + <name>bsync test in cordova environment</name> |
| 9 | <license>Apache 2.0 License</license> | 9 | <license>Apache 2.0 License</license> |
| 10 | - | ||
| 11 | - <js-module src="Rx.min.js" name="Rx"> | ||
| 12 | - <clobbers target="Rx" /> | ||
| 13 | - </js-module> | ||
| 14 | - | ||
| 15 | <js-module src="cordova-test-build.js" name="tests"> | 10 | <js-module src="cordova-test-build.js" name="tests"> |
| 16 | </js-module> | 11 | </js-module> |
| 17 | 12 | ||
| 13 | + <dependency id="cordova-plugin-file-transfer" url="https://github.com/apache/cordova-plugin-file-transfer" commit="master" /> | ||
| 14 | + | ||
| 18 | </plugin> | 15 | </plugin> |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -import { CordovaDownloader } from '../src/file-handler/cordova-file-handler'; | 1 | +import { CordovaFileHandler } from '../src/file-handler/cordova-file-handler'; |
| 2 | 2 | ||
| 3 | declare var cordova; | 3 | declare var cordova; |
| 4 | 4 | ||
| 5 | exports.defineAutoTests = function() { | 5 | exports.defineAutoTests = function() { |
| 6 | 6 | ||
| 7 | + let createFilesHelper = (filenames:Array<string>, successCallback, errorCallback) => { | ||
| 8 | + let index = 0; | ||
| 9 | + | ||
| 10 | + let create = () => { | ||
| 11 | + if (index < filenames.length) { | ||
| 12 | + console.log("try creating file: ", filenames[index]); | ||
| 13 | + createFileHelper(filenames[index], create, errorCallback); | ||
| 14 | + index += 1; | ||
| 15 | + } else { | ||
| 16 | + successCallback(); | ||
| 17 | + } | ||
| 18 | + }; | ||
| 19 | + create(); | ||
| 20 | + }; | ||
| 21 | + | ||
| 22 | + let createFileHelper = (filename, successCallback, errorCallback) => { | ||
| 23 | + window['requestFileSystem'](window['LocalFileSystem'].TEMPORARY, 0, (fs) => { | ||
| 24 | + fs.root.getFile(filename, { create: true, exclusive: false },(fileEntry) => { | ||
| 25 | + fileEntry.createWriter((fileWriter) => { | ||
| 26 | + fileWriter.onwriteend = function() { | ||
| 27 | + console.log("file created", filename); | ||
| 28 | + successCallback(); | ||
| 29 | + }; | ||
| 30 | + fileWriter.onerror = function (e) { | ||
| 31 | + console.error("file error", e); | ||
| 32 | + errorCallback(e); | ||
| 33 | + }; | ||
| 34 | + fileWriter.write(new Blob(['some arbitrary file data'], { type: 'text/plain' })); | ||
| 35 | + }); | ||
| 36 | + }, errorCallback); | ||
| 37 | + }, errorCallback); | ||
| 38 | + }; | ||
| 39 | + | ||
| 40 | + jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000; | ||
| 41 | + | ||
| 7 | describe("Cordova downloader", () => { | 42 | describe("Cordova downloader", () => { |
| 8 | 43 | ||
| 9 | - let downloader = new CordovaDownloader(); | 44 | + let handler = new CordovaFileHandler(); |
| 10 | 45 | ||
| 11 | it("should download sample image from https source and store with new name", (done) => { | 46 | it("should download sample image from https source and store with new name", (done) => { |
| 12 | 47 | ||
| 13 | - let source = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/800px-FullMoon2010.jpg"; | 48 | + let source = "https://upload.wikimedia.org/wikipedia/commons/f/ff/Pizigani_1367_Chart_10MB.jpg"; |
| 14 | - let target = cordova.file.dataDirectory + "full-moon.jpg"; | 49 | + // let target = cordova.file.dataDirectory + "full-moon.jpg"; |
| 50 | + let target = "cdvfile://localhost/temporary/full-moon.jpg"; | ||
| 15 | let lastProgress = 0; | 51 | let lastProgress = 0; |
| 16 | 52 | ||
| 17 | - downloader.download(source, target) | 53 | + handler.download(source, target) |
| 18 | - .subscribe( | 54 | + .on('progress', (progress: number) => { |
| 19 | - (progress: number) => { | 55 | + expect(progress).toBeGreaterThan(lastProgress); |
| 20 | - expect(progress).toBeGreaterThan(lastProgress); | 56 | + lastProgress = progress; |
| 21 | - lastProgress = progress; | 57 | + }).once('error', (error:any) => { |
| 22 | - } , | 58 | + fail(); |
| 23 | - (error:any) => { | 59 | + }).once('complete', () => { |
| 24 | - fail(); | 60 | + expect(lastProgress).toEqual(1); |
| 25 | - } , | 61 | + |
| 26 | - () => { | 62 | + window['resolveLocalFileSystemURL'](target, (entry:any) => { |
| 27 | - expect(lastProgress).toEqual(1); | 63 | + expect(entry.isFile).toBeTruthy(); |
| 28 | - | 64 | + expect(entry.name).toEqual("full-moon.jpg"); |
| 29 | - window['resolveLocalFileSystemURL'](target, (entry:any) => { | 65 | + done(); |
| 30 | - expect(entry.isFile).toBeTruthy(); | 66 | + }); |
| 31 | - expect(entry.name).toEqual("full-moon.jpg"); | 67 | + }); |
| 32 | - done(); | 68 | + }); |
| 33 | - }); | 69 | + |
| 34 | - } | 70 | + it('should cleanup successfully', (done) => { |
| 35 | - ); | 71 | + createFilesHelper(['file-1', 'file-2', 'file-3'], () => { |
| 72 | + console.log("files are created, now start with cleanup"); | ||
| 73 | + | ||
| 74 | + handler.once('cleanup-complete', (files) => { | ||
| 75 | + console.log(files); | ||
| 76 | + expect(files.length).toBeGreaterThan(1); | ||
| 77 | + done(); | ||
| 78 | + }); | ||
| 79 | + | ||
| 80 | + handler.once('cleanup-error', (files) => { | ||
| 81 | + fail('cleanup error'); | ||
| 82 | + }); | ||
| 36 | 83 | ||
| 84 | + handler.cleanup([ | ||
| 85 | + { source: '', target: 'file-1' } , | ||
| 86 | + { source: '', target: 'file-2' } | ||
| 87 | + ], 'cdvfile://localhost/temporary/'); | ||
| 88 | + }, (error) => { | ||
| 89 | + console.error(error); | ||
| 90 | + fail('error while creating files'); | ||
| 91 | + }); | ||
| 37 | }); | 92 | }); |
| 38 | 93 | ||
| 39 | }); | 94 | }); | ... | ... |
| 1 | -import { Observable, Subscriber } from 'rxjs'; | 1 | +import { EventEmitter } from 'events'; |
| 2 | import { FileHandler } from '../../src/api/file-handler'; | 2 | import { FileHandler } from '../../src/api/file-handler'; |
| 3 | 3 | ||
| 4 | -export class TestFileHandler implements FileHandler { | 4 | +export class TestFileHandler extends EventEmitter implements FileHandler { |
| 5 | 5 | ||
| 6 | protected static errorRate:number = 0; | 6 | protected static errorRate:number = 0; |
| 7 | 7 | ||
| ... | @@ -9,27 +9,30 @@ export class TestFileHandler implements FileHandler { | ... | @@ -9,27 +9,30 @@ export class TestFileHandler implements FileHandler { |
| 9 | TestFileHandler.errorRate = rate; | 9 | TestFileHandler.errorRate = rate; |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | - download(source:string, target:string) : Observable<number> { | 12 | + cleanup() { return this; } |
| 13 | - return Observable.create((subscriber:Subscriber<number>) => { | ||
| 14 | - let random = Math.random(); | ||
| 15 | - let error:boolean = random < TestFileHandler.errorRate; | ||
| 16 | - let counter = 1; | ||
| 17 | 13 | ||
| 18 | - if (error) { | 14 | + download(source:string, target:string) { |
| 19 | - subscriber.error("random error triggered"); | 15 | + let random = Math.random(); |
| 20 | - return; | 16 | + let error:boolean = random < TestFileHandler.errorRate; |
| 21 | - } | 17 | + let counter = 1; |
| 22 | 18 | ||
| 19 | + if (error) { | ||
| 20 | + setTimeout(() => { | ||
| 21 | + this.emit("error", "random error triggered"); | ||
| 22 | + },200); | ||
| 23 | + } else { | ||
| 23 | let interval = setInterval(() => { | 24 | let interval = setInterval(() => { |
| 24 | if (counter < 4) { | 25 | if (counter < 4) { |
| 25 | - subscriber.next(counter * 25); | 26 | + this.emit('progress', counter * 25); |
| 26 | } else { | 27 | } else { |
| 27 | - subscriber.complete(); | 28 | + this.emit('complete'); |
| 28 | clearInterval(interval); | 29 | clearInterval(interval); |
| 29 | } | 30 | } |
| 30 | 31 | ||
| 31 | ++counter; | 32 | ++counter; |
| 32 | }, 10); | 33 | }, 10); |
| 33 | - }); | 34 | + } |
| 35 | + | ||
| 36 | + return this; | ||
| 34 | } | 37 | } |
| 35 | } | 38 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | export * from './test/file-handler/node-file-handler'; | 1 | export * from './test/file-handler/node-file-handler'; |
| 2 | -export * from './test/file-replicator'; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 2 | +export * from './test/file-replicator'; | ||
| 3 | +export * from './test/util'; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -7,6 +7,10 @@ describe("Node Downloader", () => { | ... | @@ -7,6 +7,10 @@ describe("Node Downloader", () => { |
| 7 | 7 | ||
| 8 | let nodeFileHandler = new NodeFileHandler(); | 8 | let nodeFileHandler = new NodeFileHandler(); |
| 9 | 9 | ||
| 10 | + beforeEach(() => { | ||
| 11 | + nodeFileHandler.removeAllListeners(); | ||
| 12 | + }); | ||
| 13 | + | ||
| 10 | it("should retrieve right handler", () => { | 14 | it("should retrieve right handler", () => { |
| 11 | 15 | ||
| 12 | let httpHandler = nodeFileHandler.selectProtocol("http://someurl.com/image.jpg"); | 16 | let httpHandler = nodeFileHandler.selectProtocol("http://someurl.com/image.jpg"); |
| ... | @@ -23,27 +27,26 @@ describe("Node Downloader", () => { | ... | @@ -23,27 +27,26 @@ describe("Node Downloader", () => { |
| 23 | it("should download sample image from https source and store with new name", (done) => { | 27 | it("should download sample image from https source and store with new name", (done) => { |
| 24 | 28 | ||
| 25 | let source = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/800px-FullMoon2010.jpg"; | 29 | let source = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/800px-FullMoon2010.jpg"; |
| 26 | - let target = ".tmp/full-moon.jpg"; | 30 | + let target = "./.tmp/full-moon-" + (new Date()).getTime() + ".jpg"; |
| 27 | let lastProgress = 0; | 31 | let lastProgress = 0; |
| 28 | 32 | ||
| 29 | - nodeFileHandler.download(source, target) | 33 | + nodeFileHandler |
| 30 | - .subscribe( | 34 | + .on('progress', (progress: number) => { |
| 31 | - (progress: number) => { | 35 | + expect(progress).toBeGreaterThan(lastProgress); |
| 32 | - expect(progress).toBeGreaterThan(lastProgress); | 36 | + lastProgress = progress; |
| 33 | - lastProgress = progress; | 37 | + }) |
| 34 | - } , | 38 | + .once('error', (error) => {}) |
| 35 | - (error:any) => {} , | 39 | + .once('complete', () => { |
| 36 | - () => { | 40 | + expect(lastProgress).toEqual(1); |
| 37 | - expect(lastProgress).toEqual(1); | 41 | + expect(fs.existsSync(target)).toBeTruthy(); |
| 38 | - expect(fs.existsSync(target)).toBeTruthy(); | 42 | + done(); |
| 39 | - done(); | 43 | + }) |
| 40 | - } | 44 | + .download(source, target); |
| 41 | - ); | ||
| 42 | }); | 45 | }); |
| 43 | 46 | ||
| 44 | it('should not download if file with same name exists and bytesize > 0', (done) => { | 47 | it('should not download if file with same name exists and bytesize > 0', (done) => { |
| 45 | let source = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/800px-FullMoon2010.jpg"; | 48 | let source = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/800px-FullMoon2010.jpg"; |
| 46 | - let target = ".tmp/full-moon-sensless.jpg"; | 49 | + let target = "./.tmp/full-moon-sensless.jpg"; |
| 47 | let lastProgress = 0; | 50 | let lastProgress = 0; |
| 48 | 51 | ||
| 49 | let file = fs.createWriteStream(target, {'flags': 'a'}); | 52 | let file = fs.createWriteStream(target, {'flags': 'a'}); |
| ... | @@ -51,18 +54,32 @@ describe("Node Downloader", () => { | ... | @@ -51,18 +54,32 @@ describe("Node Downloader", () => { |
| 51 | 54 | ||
| 52 | file.write(new Buffer(dummyData)); | 55 | file.write(new Buffer(dummyData)); |
| 53 | file.end(() => { | 56 | file.end(() => { |
| 54 | - | 57 | + nodeFileHandler |
| 55 | - nodeFileHandler.download(source, target) | 58 | + .on('progress', () => { fail("progress should not be called"); }) |
| 56 | - .subscribe( | 59 | + .once('error', () => {}) |
| 57 | - () => { fail("progress should not be called"); } , | 60 | + .once('complete', () => { |
| 58 | - () => {} , | ||
| 59 | - () => { | ||
| 60 | expect(fs.existsSync(target)).toBeTruthy(); | 61 | expect(fs.existsSync(target)).toBeTruthy(); |
| 61 | done(); | 62 | done(); |
| 62 | - } | 63 | + }) |
| 63 | - ); | 64 | + .download(source, target); |
| 64 | - | ||
| 65 | }); | 65 | }); |
| 66 | }); | 66 | }); |
| 67 | 67 | ||
| 68 | + it('should correctly cleanup files', () => { | ||
| 69 | + let basePath = "./.tmp-files"; | ||
| 70 | + fs.mkdirSync(basePath); | ||
| 71 | + fs.writeFileSync(basePath + "/file1", "some data for file 1"); | ||
| 72 | + fs.writeFileSync(basePath + "/file2", "some data for file 2"); | ||
| 73 | + fs.writeFileSync(basePath + "/file3", "some data for file 3"); | ||
| 74 | + fs.writeFileSync(basePath + "/file4", "some data for file 4"); | ||
| 75 | + | ||
| 76 | + nodeFileHandler.cleanup([ | ||
| 77 | + { source : '' , target : 'file1' }, | ||
| 78 | + { source : '' , target : 'file2' }], basePath); | ||
| 79 | + | ||
| 80 | + let files = fs.readdirSync(basePath); | ||
| 81 | + | ||
| 82 | + expect(files.length).toEqual(2); | ||
| 83 | + }); | ||
| 84 | + | ||
| 68 | }); | 85 | }); |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | import { FileReplicator } from '../../src/file-replicator'; | 1 | import { FileReplicator } from '../../src/file-replicator'; |
| 2 | 2 | ||
| 3 | - | ||
| 4 | describe("File Replicator", () => { | 3 | describe("File Replicator", () => { |
| 5 | 4 | ||
| 6 | let fileReplicator = new FileReplicator(); | 5 | let fileReplicator = new FileReplicator(); |
| 7 | 6 | ||
| 8 | - let change = { | 7 | + let change = [ |
| 9 | - docs : [ | 8 | + { language: 'de', type : "asset", source : "http://someplace.com/icon.jpg" , target : "icon.jpg" } , |
| 10 | - { language: 'de', type : "asset", source : "http://someplace.com/icon.jpg" , target : "icon.jpg" } , | 9 | + { language: 'de', type : "asset", source : "http://sampleuri.com/image.png" } , |
| 11 | - { language: 'de', type : "asset", source : "http://sampleuri.com/image.png" } , | 10 | + { language: 'en', type : "asset", source : "https://secureasset.com/asset.mp3" , target : "music.mp3" } |
| 12 | - { language: 'en', type : "asset", source : "https://secureasset.com/asset.mp3" , target : "music.mp3" } | 11 | + ]; |
| 13 | - ] | ||
| 14 | - }; | ||
| 15 | 12 | ||
| 16 | beforeEach(() => { | 13 | beforeEach(() => { |
| 17 | - fileReplicator.init(); | 14 | + fileReplicator.clear(); |
| 18 | }); | 15 | }); |
| 19 | 16 | ||
| 20 | it("should contain several assets", () => { | 17 | it("should contain several assets", () => { |
| ... | @@ -23,7 +20,7 @@ describe("File Replicator", () => { | ... | @@ -23,7 +20,7 @@ describe("File Replicator", () => { |
| 23 | }); | 20 | }); |
| 24 | 21 | ||
| 25 | it("should get correct asset names", () => { | 22 | it("should get correct asset names", () => { |
| 26 | - let files = fileReplicator.prepareFiles(change.docs); | 23 | + let files = fileReplicator.prepareFiles(change); |
| 27 | 24 | ||
| 28 | expect(files[0].target).toEqual("icon.jpg"); | 25 | expect(files[0].target).toEqual("icon.jpg"); |
| 29 | expect(files[1].target).toEqual("bsync_707608502"); | 26 | expect(files[1].target).toEqual("bsync_707608502"); |
| ... | @@ -38,7 +35,7 @@ describe("File Replicator", () => { | ... | @@ -38,7 +35,7 @@ describe("File Replicator", () => { |
| 38 | return false; | 35 | return false; |
| 39 | }; | 36 | }; |
| 40 | 37 | ||
| 41 | - let files = fileReplicator.prepareFiles(change.docs); | 38 | + let files = fileReplicator.prepareFiles(change); |
| 42 | 39 | ||
| 43 | expect(files.length).toEqual(2); | 40 | expect(files.length).toEqual(2); |
| 44 | expect(files[0].target).toEqual("icon.jpg"); | 41 | expect(files[0].target).toEqual("icon.jpg"); | ... | ... |
spec/test/util.ts
0 → 100644
| 1 | +import { Util } from '../../src/util'; | ||
| 2 | + | ||
| 3 | +describe('Util', () => { | ||
| 4 | + | ||
| 5 | + let files = [ | ||
| 6 | + { source : '' , target : 'file-1'} , | ||
| 7 | + { source : '' , target : 'file-2'} , | ||
| 8 | + { source : '' , target : 'file-3'} , | ||
| 9 | + ]; | ||
| 10 | + | ||
| 11 | + it('should show files to remove', () => { | ||
| 12 | + let cleanupFiles = Util.getFilesForCleanup(files, ['file-1','file-4']); | ||
| 13 | + expect(cleanupFiles[0]).toEqual('file-4'); | ||
| 14 | + }); | ||
| 15 | + | ||
| 16 | +}); | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | -import { Observable } from 'rxjs/Observable'; | 1 | +import { File } from './file'; |
| 2 | 2 | ||
| 3 | export interface FileHandler { | 3 | export interface FileHandler { |
| 4 | 4 | ||
| ... | @@ -8,6 +8,17 @@ export interface FileHandler { | ... | @@ -8,6 +8,17 @@ export interface FileHandler { |
| 8 | * - if the download is in progress: trigger next (0-1 progress for percent of download) | 8 | * - if the download is in progress: trigger next (0-1 progress for percent of download) |
| 9 | * - if the download enters any error condition, trigger error and an already downloaded part of the file | 9 | * - if the download enters any error condition, trigger error and an already downloaded part of the file |
| 10 | */ | 10 | */ |
| 11 | - download(source:string, target:string) : Observable<number>; | 11 | + download(source:string, target:string) : FileHandler; |
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * Remove all files, which are not inside the files array from local storage | ||
| 15 | + */ | ||
| 16 | + cleanup(files: Array<File>, basePath?: string); | ||
| 17 | + | ||
| 18 | + on(event:string, handler:(...params: any[]) => void) : FileHandler; | ||
| 19 | + | ||
| 20 | + once(event:string, handler:(...params: any[]) => void) : FileHandler; | ||
| 21 | + | ||
| 22 | + removeAllListeners() : FileHandler; | ||
| 12 | 23 | ||
| 13 | } | 24 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -import { EventEmitter } from 'events'; | ||
| 2 | import { ServiceLocator } from './service-locator'; | 1 | import { ServiceLocator } from './service-locator'; |
| 3 | 2 | ||
| 4 | export * from './service-locator'; | 3 | export * from './service-locator'; |
| 5 | 4 | ||
| 6 | -export function loadBsyncPlugin (PouchDB) { | 5 | +export * from './config'; |
| 7 | - let pouchReplicate = PouchDB.replicate; | ||
| 8 | - | ||
| 9 | - PouchDB.plugin((PouchDB) => { | ||
| 10 | - PouchDB.replicate = function() { | ||
| 11 | - let eventEmitter = new EventEmitter(); | ||
| 12 | - let emitter = pouchReplicate.apply(this, arguments); | ||
| 13 | - let replicator = ServiceLocator.getFileReplicator(); | ||
| 14 | - let db = arguments[1]; | ||
| 15 | - | ||
| 16 | - replicator.once('final', event => { | ||
| 17 | - eventEmitter.emit('complete'); | ||
| 18 | - eventEmitter.removeAllListeners(); | ||
| 19 | - }); | ||
| 20 | - | ||
| 21 | - replicator.on('error', event => { | ||
| 22 | - eventEmitter.emit('file-replicator-error', event); | ||
| 23 | - }); | ||
| 24 | - | ||
| 25 | - replicator.on('complete', event => { | ||
| 26 | - eventEmitter.emit('file-replicator-complete', event); | ||
| 27 | - }); | ||
| 28 | - | ||
| 29 | - replicator.on('progress', event => { | ||
| 30 | - eventEmitter.emit('file-replicator-progress', event); | ||
| 31 | - }); | ||
| 32 | - | ||
| 33 | - emitter.once('change', info => { | ||
| 34 | - eventEmitter.emit('change', info); | ||
| 35 | - }); | ||
| 36 | - | ||
| 37 | - emitter.once('complete', info => { | ||
| 38 | - db.query('index_type/type',{ | ||
| 39 | - include_docs : true, | ||
| 40 | - key : replicator.itemValue | ||
| 41 | - }).then((res) => { | ||
| 42 | - let docs = { docs : [] }; | ||
| 43 | - for (let r of res.rows) { | ||
| 44 | - docs.docs.push(r.doc); | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - replicator.pushChanges(docs); | ||
| 48 | - replicator.start(); | ||
| 49 | - }).catch(error => { | ||
| 50 | - eventEmitter.emit('error', error); | ||
| 51 | - }); | ||
| 52 | - }); | ||
| 53 | - | ||
| 54 | - emitter.once('error', (error) => { | ||
| 55 | - eventEmitter.emit('error', error); | ||
| 56 | - }); | ||
| 57 | - | ||
| 58 | - return eventEmitter; | ||
| 59 | - }; | ||
| 60 | - }); | ||
| 61 | -}; | ||
| 62 | - | ||
| 63 | -if (typeof window !== 'undefined' && window['PouchDB']) { | ||
| 64 | - loadBsyncPlugin(window['PouchDB']); | ||
| 65 | -} | ... | ... |
| ... | @@ -5,24 +5,26 @@ export const CONFIG_ITEM_TARGET_ATTRIBUTE = "itemTargetAttribute"; | ... | @@ -5,24 +5,26 @@ export const CONFIG_ITEM_TARGET_ATTRIBUTE = "itemTargetAttribute"; |
| 5 | export const CONFIG_ITEM_VALIDATOR = "itemValidator"; | 5 | export const CONFIG_ITEM_VALIDATOR = "itemValidator"; |
| 6 | export const CONFIG_RETRY_TIMEOUT = "retryTimeout"; | 6 | export const CONFIG_RETRY_TIMEOUT = "retryTimeout"; |
| 7 | export const CONFIG_FILE_HANDLER = "fileHandler"; | 7 | export const CONFIG_FILE_HANDLER = "fileHandler"; |
| 8 | +export const CONFIG_TARGET_DIRECTORY = "targetDirectory"; | ||
| 8 | 9 | ||
| 9 | export class Config { | 10 | export class Config { |
| 10 | 11 | ||
| 11 | protected config:any = {}; | 12 | protected config:any = {}; |
| 12 | 13 | ||
| 13 | - hasConfig(key:string) { | 14 | + hasConfig(key:string) : boolean { |
| 14 | if (this.config[key]) { | 15 | if (this.config[key]) { |
| 15 | return true; | 16 | return true; |
| 16 | } | 17 | } |
| 17 | return false; | 18 | return false; |
| 18 | } | 19 | } |
| 19 | 20 | ||
| 20 | - getConfig(key:string) { | 21 | + getConfig(key:string) : any { |
| 21 | return this.config[key]; | 22 | return this.config[key]; |
| 22 | } | 23 | } |
| 23 | 24 | ||
| 24 | - setConfig(key:string, value:any) { | 25 | + setConfig(key:string, value:any) : Config { |
| 25 | this.config[key] = value; | 26 | this.config[key] = value; |
| 27 | + return this; | ||
| 26 | } | 28 | } |
| 27 | 29 | ||
| 28 | } | 30 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | +import { Util } from './../util'; | ||
| 2 | +import { EventEmitter } from 'events'; | ||
| 1 | import { FileHandler } from '../api/file-handler'; | 3 | import { FileHandler } from '../api/file-handler'; |
| 4 | +import { File } from '../api/file'; | ||
| 2 | 5 | ||
| 3 | -declare var Rx; | 6 | +export class CordovaFileHandler extends EventEmitter implements FileHandler { |
| 4 | 7 | ||
| 5 | -export class CordovaDownloader implements FileHandler { | 8 | + triggerFileTransfer(source:string, target:string) { |
| 9 | + let fileTransfer = new window['FileTransfer'](); | ||
| 6 | 10 | ||
| 7 | - download(source:string, target:string) : Rx.Observable<number> { | 11 | + fileTransfer.onprogress = (progress:ProgressEvent) => { |
| 8 | - return Rx.Observable.create((subscriber:Rx.Subscriber<number>) => { | 12 | + this.emit('progress', progress.loaded / progress.total); |
| 13 | + }; | ||
| 9 | 14 | ||
| 10 | - if (!window['FileTransfer']) { | 15 | + fileTransfer.download( |
| 11 | - subscriber.error("Cordova FileTransfer object undefined"); | 16 | + source , |
| 17 | + target , | ||
| 18 | + (entry:any) => { | ||
| 19 | + this.emit('complete', entry); | ||
| 20 | + } , | ||
| 21 | + (error:any) => { | ||
| 22 | + this.emit('error', error); | ||
| 23 | + }, | ||
| 24 | + true | ||
| 25 | + ); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + download(source:string, target:string) { | ||
| 29 | + if (!window['FileTransfer']) { | ||
| 30 | + this.emit('error','Cordova FileTransfer object undefined'); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + window['resolveLocalFileSystemURL'](target, (entry:any) => { | ||
| 34 | + entry.getMetadata((metadata) => { | ||
| 35 | + if (metadata.size > 0) { | ||
| 36 | + this.emit('complete', entry); | ||
| 37 | + } else { | ||
| 38 | + // file empty trigger transfer | ||
| 39 | + this.triggerFileTransfer(source,target); | ||
| 40 | + } | ||
| 41 | + }, () => { | ||
| 42 | + // cannot read metadata trigger transfer | ||
| 43 | + this.triggerFileTransfer(source,target); | ||
| 44 | + }); | ||
| 45 | + }, () => { | ||
| 46 | + // file not found, so download it | ||
| 47 | + this.triggerFileTransfer(source,target); | ||
| 48 | + }); | ||
| 49 | + | ||
| 50 | + return this; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + cleanup(files:Array<File>, basePath:string) : Promise<void> { | ||
| 54 | + let filesForCleanup = []; | ||
| 55 | + | ||
| 56 | + return new Promise<void>((resolve, reject) => { | ||
| 57 | + | ||
| 58 | + if (window['resolveLocalFileSystemURL']) { | ||
| 59 | + window['resolveLocalFileSystemURL'](basePath, (entry) => { | ||
| 60 | + let reader = entry.createReader(); | ||
| 61 | + reader.readEntries((entries) => { | ||
| 62 | + | ||
| 63 | + for (let e of entries) { | ||
| 64 | + if (e && e.isFile) { | ||
| 65 | + if (Util.getFileIndex(files, e.name) < 0) { | ||
| 66 | + filesForCleanup.push(e); | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + let index = 0; | ||
| 72 | + let error = false; | ||
| 73 | + | ||
| 74 | + let cleanupError = (error) => { | ||
| 75 | + this.emit('cleanup-error', error); | ||
| 76 | + reject(); | ||
| 77 | + error = true; | ||
| 78 | + }; | ||
| 79 | + | ||
| 80 | + let cleanupNext = () => { | ||
| 81 | + if (index < filesForCleanup.length && !error) { | ||
| 82 | + filesForCleanup[index].remove(cleanupNext, cleanupError); | ||
| 83 | + index += 1; | ||
| 84 | + } else if (!error) { | ||
| 85 | + this.emit('cleanup-complete', filesForCleanup); | ||
| 86 | + resolve(); | ||
| 87 | + } | ||
| 88 | + }; | ||
| 89 | + | ||
| 90 | + cleanupNext(); | ||
| 91 | + | ||
| 92 | + }, (error) => { this.emit('cleanup-error', error); reject(); }); | ||
| 93 | + }); | ||
| 12 | } | 94 | } |
| 13 | 95 | ||
| 14 | - let fileTransfer = new window['FileTransfer'](); | 96 | + }); |
| 15 | - | ||
| 16 | - fileTransfer.onprogress = (progress:ProgressEvent) => { | ||
| 17 | - subscriber.next(progress.loaded / progress.total); | ||
| 18 | - }; | ||
| 19 | - | ||
| 20 | - fileTransfer.download( | ||
| 21 | - source , | ||
| 22 | - target , | ||
| 23 | - (entry:any) => { | ||
| 24 | - subscriber.complete(); | ||
| 25 | - } , | ||
| 26 | - (error:any) => { | ||
| 27 | - subscriber.error(error); | ||
| 28 | - }, | ||
| 29 | - true | ||
| 30 | - ); | ||
| 31 | - | ||
| 32 | - }); | ||
| 33 | } | 97 | } |
| 34 | 98 | ||
| 35 | } | 99 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -import { Observable, Subscriber } from 'rxjs'; | 1 | +import { EventEmitter } from 'events'; |
| 2 | import { FileHandler } from '../api/file-handler'; | 2 | import { FileHandler } from '../api/file-handler'; |
| 3 | +import { File } from '../api/file'; | ||
| 3 | 4 | ||
| 4 | -export class ElectronFileHandler implements FileHandler { | 5 | +export class ElectronFileHandler extends EventEmitter implements FileHandler { |
| 5 | 6 | ||
| 6 | constructor (private ipcRenderer:any) { | 7 | constructor (private ipcRenderer:any) { |
| 8 | + super(); | ||
| 7 | } | 9 | } |
| 8 | 10 | ||
| 9 | - download(source:string, target:string) : Observable<number> { | 11 | + download(source:string, target:string) { |
| 10 | - return Observable.create((subscriber:Subscriber<number>) => { | 12 | + this.ipcRenderer.once('bsync-download-complete', () => { |
| 13 | + this.ipcRenderer.removeAllListeners('bsync-download-progress'); | ||
| 14 | + this.ipcRenderer.removeAllListeners('bsync-download-error'); | ||
| 15 | + this.emit('complete'); | ||
| 16 | + }); | ||
| 11 | 17 | ||
| 12 | - this.ipcRenderer.once('bsync-download-complete', () => { | 18 | + this.ipcRenderer.on('bsync-download-progress', (progress:number) => { |
| 13 | - this.ipcRenderer.removeAllListeners('bsync-download-progress'); | 19 | + this.emit('progress', progress); |
| 14 | - this.ipcRenderer.removeAllListeners('bsync-download-error'); | 20 | + }); |
| 15 | - subscriber.complete(); | ||
| 16 | - }); | ||
| 17 | 21 | ||
| 18 | - this.ipcRenderer.on('bsync-download-progress', (progress:number) => { | 22 | + this.ipcRenderer.once('bsync-download-error', (error:any) => { |
| 19 | - subscriber.next(progress); | 23 | + this.ipcRenderer.removeAllListeners('bsync-download-progress'); |
| 20 | - }); | 24 | + this.ipcRenderer.removeAllListeners('bsync-download-complete'); |
| 25 | + this.emit('error', error); | ||
| 26 | + }); | ||
| 21 | 27 | ||
| 22 | - this.ipcRenderer.once('bsync-download-error', (error:any) => { | 28 | + this.ipcRenderer.send('bsync-download', { |
| 23 | - this.ipcRenderer.removeAllListeners('bsync-download-progress'); | 29 | + source : source , |
| 24 | - this.ipcRenderer.removeAllListeners('bsync-download-complete'); | 30 | + target : target |
| 25 | - subscriber.error(error); | 31 | + }); |
| 26 | - }); | 32 | + |
| 33 | + return this; | ||
| 34 | + } | ||
| 27 | 35 | ||
| 28 | - this.ipcRenderer.send('bsync-download', { | 36 | + cleanup(files:Array<File>) : Promise<void> { |
| 29 | - source : source , | 37 | + return new Promise<void>((resolve, reject) => { |
| 30 | - target : target | 38 | + |
| 39 | + this.ipcRenderer.once('bsync-cleanup-complete', () => { | ||
| 40 | + this.emit('cleanup-complete'); | ||
| 41 | + resolve(); | ||
| 31 | }); | 42 | }); |
| 43 | + | ||
| 44 | + this.ipcRenderer.send('bsync-cleanup', files); | ||
| 45 | + | ||
| 32 | }); | 46 | }); |
| 33 | } | 47 | } |
| 34 | 48 | ... | ... |
| 1 | -import { Observable } from 'rxjs/Observable'; | 1 | +import { Util } from './../util'; |
| 2 | -import { Subscriber } from 'rxjs/Subscriber'; | 2 | +import { EventEmitter } from 'events'; |
| 3 | import { FileHandler } from '../api/file-handler'; | 3 | import { FileHandler } from '../api/file-handler'; |
| 4 | +import { File } from '../api/file'; | ||
| 4 | import * as http from 'http'; | 5 | import * as http from 'http'; |
| 5 | import * as https from 'https'; | 6 | import * as https from 'https'; |
| 6 | import * as fs from 'fs'; | 7 | import * as fs from 'fs'; |
| 7 | 8 | ||
| 8 | -export class NodeFileHandler implements FileHandler { | 9 | +export class NodeFileHandler extends EventEmitter implements FileHandler { |
| 9 | 10 | ||
| 10 | selectProtocol(url:string) : any { | 11 | selectProtocol(url:string) : any { |
| 11 | if (url.search(/^http:\/\//) === 0) { | 12 | if (url.search(/^http:\/\//) === 0) { |
| ... | @@ -17,23 +18,20 @@ export class NodeFileHandler implements FileHandler { | ... | @@ -17,23 +18,20 @@ export class NodeFileHandler implements FileHandler { |
| 17 | } | 18 | } |
| 18 | } | 19 | } |
| 19 | 20 | ||
| 20 | - download(source:string, target:string) : Observable<number> { | 21 | + download(source:string, target:string) { |
| 21 | 22 | ||
| 22 | let handler = this.selectProtocol(source); | 23 | let handler = this.selectProtocol(source); |
| 23 | 24 | ||
| 24 | - return Observable.create((subscriber:Subscriber<number>) => { | 25 | + if (!handler) { |
| 25 | - | 26 | + this.emit("error","No handler for source: " + source); |
| 26 | - if (!handler) { | 27 | + return this; |
| 27 | - subscriber.error("No handler for source: " + source); | 28 | + } |
| 28 | - return; | ||
| 29 | - } | ||
| 30 | - | ||
| 31 | - // file already exists and is not empty | ||
| 32 | - if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) { | ||
| 33 | - subscriber.complete(); | ||
| 34 | - return; | ||
| 35 | - } | ||
| 36 | 29 | ||
| 30 | + // file already exists and is not empty | ||
| 31 | + if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) { | ||
| 32 | + this.emit("complete"); | ||
| 33 | + return this; | ||
| 34 | + } else { | ||
| 37 | let file = fs.createWriteStream(target, {'flags': 'a'}); | 35 | let file = fs.createWriteStream(target, {'flags': 'a'}); |
| 38 | 36 | ||
| 39 | handler.get(source, (response) => { | 37 | handler.get(source, (response) => { |
| ... | @@ -41,29 +39,42 @@ export class NodeFileHandler implements FileHandler { | ... | @@ -41,29 +39,42 @@ export class NodeFileHandler implements FileHandler { |
| 41 | let prog = 0; // already downloaded | 39 | let prog = 0; // already downloaded |
| 42 | let progCounts = 100; // how many progress events should be triggerd (1-100 %) | 40 | let progCounts = 100; // how many progress events should be triggerd (1-100 %) |
| 43 | let nextProg = (1/progCounts); | 41 | let nextProg = (1/progCounts); |
| 44 | - | 42 | + |
| 45 | response.on('data', (chunk) => { | 43 | response.on('data', (chunk) => { |
| 46 | prog += chunk.length; | 44 | prog += chunk.length; |
| 47 | file.write(chunk, 'binary'); | 45 | file.write(chunk, 'binary'); |
| 48 | 46 | ||
| 49 | if ((prog / size) > nextProg) { | 47 | if ((prog / size) > nextProg) { |
| 50 | - subscriber.next(prog / size); | 48 | + this.emit('progress',prog / size); |
| 51 | nextProg += (1 / progCounts); | 49 | nextProg += (1 / progCounts); |
| 52 | } | 50 | } |
| 53 | }); | 51 | }); |
| 54 | 52 | ||
| 55 | - response.on('end', () => { | 53 | + response.once('end', () => { |
| 56 | file.end(); | 54 | file.end(); |
| 57 | - subscriber.complete(); | 55 | + this.emit('complete'); |
| 58 | - }); | 56 | + }); |
| 59 | - | 57 | + |
| 60 | }).on('error', (error) => { | 58 | }).on('error', (error) => { |
| 61 | fs.unlink(target); | 59 | fs.unlink(target); |
| 62 | - subscriber.error("Error while downloading: " + error); | 60 | + this.emit("error", "Error while downloading: " + error); |
| 63 | }); | 61 | }); |
| 64 | 62 | ||
| 65 | - }); | 63 | + return this; |
| 64 | + } | ||
| 65 | + } | ||
| 66 | 66 | ||
| 67 | + cleanup(files:Array<File>, basePath?:string) { | ||
| 68 | + try { | ||
| 69 | + let localFiles = fs.readdirSync(basePath); | ||
| 70 | + Util.getFilesForCleanup(files, localFiles) | ||
| 71 | + .forEach((file) => { | ||
| 72 | + fs.unlinkSync(basePath + "/" + file); | ||
| 73 | + }); | ||
| 74 | + } catch (e) { | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + return this; | ||
| 67 | } | 78 | } |
| 68 | 79 | ||
| 69 | } | 80 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -import {FileHandler} from './api/file-handler'; | 1 | +import { FileHandler } from './api/file-handler'; |
| 2 | -import {File} from './api/file'; | 2 | +import { File } from './api/file'; |
| 3 | -import {Util} from './util'; | 3 | +import { Util } from './util'; |
| 4 | -import {EventEmitter} from 'events'; | 4 | +import { EventEmitter } from 'events'; |
| 5 | 5 | ||
| 6 | export class FileReplicator extends EventEmitter { | 6 | export class FileReplicator extends EventEmitter { |
| 7 | 7 | ||
| ... | @@ -9,46 +9,52 @@ export class FileReplicator extends EventEmitter { | ... | @@ -9,46 +9,52 @@ export class FileReplicator extends EventEmitter { |
| 9 | super(); | 9 | super(); |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | - protected _files:Array<File> = []; | 12 | + protected _files: Array<File> = []; |
| 13 | 13 | ||
| 14 | - protected _itemValidator: (item:any) => boolean = null; | 14 | + protected _itemValidator: (item: any) => boolean = null; |
| 15 | - protected _fileHandler:FileHandler = null; | 15 | + protected _fileHandler: FileHandler = null; |
| 16 | - protected _retryTimeout:number = 0; | 16 | + protected _retryTimeout: number = 100; |
| 17 | - | 17 | + protected _retries: number = 10; |
| 18 | - protected _itemKey = "type"; | 18 | + |
| 19 | + protected _itemKey = "type"; | ||
| 19 | protected _itemValue = "asset"; | 20 | protected _itemValue = "asset"; |
| 20 | protected _itemSourceAttribute = "source"; | 21 | protected _itemSourceAttribute = "source"; |
| 21 | protected _itemTargetAttribute = "target"; | 22 | protected _itemTargetAttribute = "target"; |
| 23 | + protected _targetDirectory = ""; | ||
| 22 | 24 | ||
| 23 | get files(): Array<File> { | 25 | get files(): Array<File> { |
| 24 | return this._files; | 26 | return this._files; |
| 25 | } | 27 | } |
| 26 | 28 | ||
| 27 | - set fileHandler (handler:FileHandler) { | 29 | + set fileHandler(handler: FileHandler) { |
| 28 | this._fileHandler = handler; | 30 | this._fileHandler = handler; |
| 29 | } | 31 | } |
| 30 | 32 | ||
| 31 | - set retryTimeout (timeout:number) { | 33 | + get fileHandler() : FileHandler { |
| 34 | + return this._fileHandler; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + set retryTimeout(timeout: number) { | ||
| 32 | this._retryTimeout = timeout; | 38 | this._retryTimeout = timeout; |
| 33 | } | 39 | } |
| 34 | 40 | ||
| 35 | - set itemValidator(validator:(item:any) => boolean) { | 41 | + set itemValidator(validator: (item: any) => boolean) { |
| 36 | this._itemValidator = validator; | 42 | this._itemValidator = validator; |
| 37 | } | 43 | } |
| 38 | 44 | ||
| 39 | - set itemKey(key:string) { | 45 | + set itemKey(key: string) { |
| 40 | this._itemKey = key; | 46 | this._itemKey = key; |
| 41 | } | 47 | } |
| 42 | 48 | ||
| 43 | - set itemValue(value:string) { | 49 | + set itemValue(value: string) { |
| 44 | this._itemValue = value; | 50 | this._itemValue = value; |
| 45 | } | 51 | } |
| 46 | 52 | ||
| 47 | - set itemSourceAttribute(sourceAttribute:string) { | 53 | + set itemSourceAttribute(sourceAttribute: string) { |
| 48 | this._itemSourceAttribute = sourceAttribute; | 54 | this._itemSourceAttribute = sourceAttribute; |
| 49 | } | 55 | } |
| 50 | 56 | ||
| 51 | - set itemTargetAttribute(targetAttribute:string) { | 57 | + set itemTargetAttribute(targetAttribute: string) { |
| 52 | this._itemTargetAttribute = targetAttribute; | 58 | this._itemTargetAttribute = targetAttribute; |
| 53 | } | 59 | } |
| 54 | 60 | ||
| ... | @@ -68,24 +74,32 @@ export class FileReplicator extends EventEmitter { | ... | @@ -68,24 +74,32 @@ export class FileReplicator extends EventEmitter { |
| 68 | return this._itemTargetAttribute; | 74 | return this._itemTargetAttribute; |
| 69 | } | 75 | } |
| 70 | 76 | ||
| 71 | - init(files: Array<File> = []) { | 77 | + set targetDirectory(targetDirectory: string) { |
| 78 | + this._targetDirectory = targetDirectory; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + get targetDirectory() { | ||
| 82 | + return this._targetDirectory; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + clear(files: Array<File> = []) { | ||
| 72 | this._files = files; | 86 | this._files = files; |
| 73 | } | 87 | } |
| 74 | 88 | ||
| 75 | /** | 89 | /** |
| 76 | * change from pouchdb replicate | 90 | * change from pouchdb replicate |
| 77 | */ | 91 | */ |
| 78 | - pushChanges(change:any) { | 92 | + pushChanges(docs: Array<any>) { |
| 79 | - let items:Array<any> = []; | 93 | + let items: Array<any> = []; |
| 80 | 94 | ||
| 81 | - if (change && change.docs && change.docs.length > 0) { | 95 | + if (docs && docs.length > 0) { |
| 82 | - for (let item of change.docs) { | 96 | + for (let item of docs) { |
| 83 | if (item[this._itemKey] && item[this._itemKey] === this._itemValue) { | 97 | if (item[this._itemKey] && item[this._itemKey] === this._itemValue) { |
| 84 | items.push(item); | 98 | items.push(item); |
| 85 | } | 99 | } |
| 86 | } | 100 | } |
| 87 | } | 101 | } |
| 88 | - | 102 | + |
| 89 | let files = this.prepareFiles(items); | 103 | let files = this.prepareFiles(items); |
| 90 | 104 | ||
| 91 | for (let file of files) { | 105 | for (let file of files) { |
| ... | @@ -93,35 +107,35 @@ export class FileReplicator extends EventEmitter { | ... | @@ -93,35 +107,35 @@ export class FileReplicator extends EventEmitter { |
| 93 | } | 107 | } |
| 94 | } | 108 | } |
| 95 | 109 | ||
| 96 | - downloadFiles(files:Array<File>, fileHandler:FileHandler, index:number = 0) { | 110 | + downloadFiles(files: Array<File>, fileHandler: FileHandler, index: number = 0) { |
| 97 | if (index >= files.length) { | 111 | if (index >= files.length) { |
| 98 | return; | 112 | return; |
| 99 | } | 113 | } |
| 100 | 114 | ||
| 101 | - this.emit('start', { progress: 0, index : index, length : files.length }); | 115 | + this.emit('start', { progress: 0, index: index, length: files.length }); |
| 102 | - | 116 | + |
| 103 | fileHandler | 117 | fileHandler |
| 104 | - .download(files[index].source, files[index].target) | 118 | + .on('progress', (progress) => { |
| 105 | - .subscribe( | 119 | + this.emit('file-progress', { progress: progress, index: index, length: files.length }) |
| 106 | - progress => { | 120 | + }) |
| 107 | - this.emit('progress', { progress : progress, index : index, length : files.length }) | 121 | + .once('error', error => { |
| 108 | - } , | 122 | + this.emit('file-error', { progress: 0, index: index, length: files.length, error: error }); |
| 109 | - error => { | 123 | + fileHandler.removeAllListeners(); |
| 110 | - this.emit('error', { progress : 0, index : index, length : files.length, error: error }); | 124 | + }) |
| 111 | - } , | 125 | + .once('complete', () => { |
| 112 | - () => { | 126 | + this.emit('file-complete', { progress: 100, index: index, length: files.length }); |
| 113 | - this.emit('complete', { progress : 100 , index : index, length : files.length }); | 127 | + fileHandler.removeAllListeners(); |
| 114 | - this.downloadFiles(files, fileHandler, index+1); | 128 | + this.downloadFiles(files, fileHandler, index + 1); |
| 115 | - } | 129 | + }) |
| 116 | - ); | 130 | + .download(files[index].source, this.targetDirectory + files[index].target); |
| 117 | - } | 131 | + } |
| 118 | - | 132 | + |
| 119 | - prepareFiles(items: Array<any>) : Array<File> { | 133 | + prepareFiles(items: Array<any>): Array<File> { |
| 120 | let output = []; | 134 | let output = []; |
| 121 | 135 | ||
| 122 | for (let item of items) { | 136 | for (let item of items) { |
| 123 | if (item[this._itemSourceAttribute] && (!this._itemValidator || this._itemValidator(item))) { | 137 | if (item[this._itemSourceAttribute] && (!this._itemValidator || this._itemValidator(item))) { |
| 124 | - let file = { source : item[this._itemSourceAttribute] , target : '' }; | 138 | + let file = { source: item[this._itemSourceAttribute], target: '' }; |
| 125 | 139 | ||
| 126 | if (item[this._itemTargetAttribute]) { | 140 | if (item[this._itemTargetAttribute]) { |
| 127 | file.target = item[this._itemTargetAttribute]; | 141 | file.target = item[this._itemTargetAttribute]; |
| ... | @@ -136,30 +150,48 @@ export class FileReplicator extends EventEmitter { | ... | @@ -136,30 +150,48 @@ export class FileReplicator extends EventEmitter { |
| 136 | return output; | 150 | return output; |
| 137 | } | 151 | } |
| 138 | 152 | ||
| 139 | - start() { | 153 | + start(retries:number = 10) { |
| 140 | - this.on('complete', (event:any) => { | 154 | + this._retries = retries; |
| 155 | + | ||
| 156 | + this.on('file-complete', (event: any) => { | ||
| 141 | if ((event.index + 1) >= event.length) { | 157 | if ((event.index + 1) >= event.length) { |
| 142 | this.replicationFinalized(event.index); | 158 | this.replicationFinalized(event.index); |
| 143 | } | 159 | } |
| 144 | }); | 160 | }); |
| 145 | 161 | ||
| 146 | - this.on('error', (event:any) => { | 162 | + this.on('file-error', (event: any) => { |
| 147 | this.replicationFinalized(event.index); | 163 | this.replicationFinalized(event.index); |
| 148 | - }); | 164 | + }); |
| 149 | 165 | ||
| 150 | - this.downloadFiles(this._files, this._fileHandler); | 166 | + if (this._fileHandler && this._files.length > 0) { |
| 167 | + this.downloadFiles(this._files, this._fileHandler); | ||
| 168 | + } else { | ||
| 169 | + this.emit('complete'); | ||
| 170 | + } | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + cleanup() { | ||
| 174 | + this.fileHandler | ||
| 175 | + .cleanup(this._files, this._targetDirectory) | ||
| 176 | + .then(() => { | ||
| 177 | + this.emit('cleanup-complete'); | ||
| 178 | + }).catch(() => { | ||
| 179 | + this.emit('cleanup-error'); | ||
| 180 | + }); | ||
| 151 | } | 181 | } |
| 152 | 182 | ||
| 153 | - replicationFinalized(lastIndex:number) { | 183 | + replicationFinalized(lastIndex: number) { |
| 154 | - if (lastIndex+1 >= this._files.length) { // all finished | 184 | + if (lastIndex + 1 >= this._files.length) { // all finished |
| 155 | - this._files = []; | 185 | + this.emit('complete'); |
| 156 | - this.emit('final'); | 186 | + } else if (this._retries > 0) { // restart after last success |
| 157 | - } else if (this._retryTimeout > 0) { // restart after last success | 187 | + this._files.splice(0, lastIndex); |
| 158 | - this._files.splice(0,lastIndex); | 188 | + setTimeout(() => { |
| 159 | - setTimeout(() => { | 189 | + this._retries =- 1; |
| 160 | this.downloadFiles(this._files, this._fileHandler); | 190 | this.downloadFiles(this._files, this._fileHandler); |
| 161 | }, this._retryTimeout); | 191 | }, this._retryTimeout); |
| 162 | - } | 192 | + } else { |
| 193 | + this.emit('error'); | ||
| 194 | + } | ||
| 163 | } | 195 | } |
| 164 | 196 | ||
| 165 | } | 197 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -2,17 +2,22 @@ import { NodeFileHandler } from './file-handler/node-file-handler'; | ... | @@ -2,17 +2,22 @@ import { NodeFileHandler } from './file-handler/node-file-handler'; |
| 2 | 2 | ||
| 3 | export default class Bsync { | 3 | export default class Bsync { |
| 4 | 4 | ||
| 5 | - static configIpcMain(ipcMain: any, downloadDir:string) { | 5 | + static configIpcMain(ipcMain: any, basePath: string) { |
| 6 | - let nodeFileHander = new NodeFileHandler(); | 6 | + |
| 7 | - | ||
| 8 | ipcMain.on('bsync-download', (event, args) => { | 7 | ipcMain.on('bsync-download', (event, args) => { |
| 9 | - nodeFileHander.download(args.source, downloadDir + args.target) | 8 | + let nodeFileHander = new NodeFileHandler(); |
| 10 | - .subscribe( | 9 | + nodeFileHander.download(args.source, basePath + "/" + args.target) |
| 11 | - (progress:number) => { event.sender.send('bsync-download-progress', progress); } , | 10 | + .on('progress', (progress:number) => { event.sender.send('bsync-download-progress', progress); }) |
| 12 | - (error:any) => { event.sender.send('bsync-download-error', error); } , | 11 | + .once('error', (error) => { nodeFileHander.removeAllListeners(); event.sender.send('bsync-download-error', error); }) |
| 13 | - () => { event.sender.send('bsync-download-complete'); } | 12 | + .once('complete', () => { nodeFileHander.removeAllListeners(); event.sender.send('bsync-download-complete'); }); |
| 14 | - ); | 13 | + }); |
| 14 | + | ||
| 15 | + ipcMain.on('bsync-cleanup', (event, args) => { | ||
| 16 | + let nodeFileHandler = new NodeFileHandler(); | ||
| 17 | + nodeFileHandler.cleanup(args.files, basePath); | ||
| 18 | + event.sender.send('bsync-cleanup-complete'); | ||
| 15 | }); | 19 | }); |
| 20 | + | ||
| 16 | } | 21 | } |
| 17 | 22 | ||
| 18 | } | 23 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | import {FileHandler} from './api/file-handler'; | 1 | import {FileHandler} from './api/file-handler'; |
| 2 | import {ElectronFileHandler} from './file-handler/electron-file-handler'; | 2 | import {ElectronFileHandler} from './file-handler/electron-file-handler'; |
| 3 | -import {CordovaDownloader} from './file-handler/cordova-file-handler'; | 3 | +import {CordovaFileHandler} from './file-handler/cordova-file-handler'; |
| 4 | import {FileReplicator} from './file-replicator'; | 4 | import {FileReplicator} from './file-replicator'; |
| 5 | import { | 5 | import { |
| 6 | Config, | 6 | Config, |
| ... | @@ -8,7 +8,8 @@ import { | ... | @@ -8,7 +8,8 @@ import { |
| 8 | CONFIG_ITEM_KEY, | 8 | CONFIG_ITEM_KEY, |
| 9 | CONFIG_ITEM_VALUE, | 9 | CONFIG_ITEM_VALUE, |
| 10 | CONFIG_ITEM_TARGET_ATTRIBUTE, | 10 | CONFIG_ITEM_TARGET_ATTRIBUTE, |
| 11 | - CONFIG_ITEM_SOURCE_ATTRIBUTE | 11 | + CONFIG_ITEM_SOURCE_ATTRIBUTE, |
| 12 | + CONFIG_TARGET_DIRECTORY | ||
| 12 | } from './config'; | 13 | } from './config'; |
| 13 | 14 | ||
| 14 | export const ENV_ELECTRON = "electron"; | 15 | export const ENV_ELECTRON = "electron"; |
| ... | @@ -56,7 +57,7 @@ export class ServiceLocator { | ... | @@ -56,7 +57,7 @@ export class ServiceLocator { |
| 56 | } | 57 | } |
| 57 | 58 | ||
| 58 | if (environment === ENV_CORDOVA) { | 59 | if (environment === ENV_CORDOVA) { |
| 59 | - return new CordovaDownloader(); | 60 | + return new CordovaFileHandler(); |
| 60 | } | 61 | } |
| 61 | 62 | ||
| 62 | return null; | 63 | return null; |
| ... | @@ -64,28 +65,29 @@ export class ServiceLocator { | ... | @@ -64,28 +65,29 @@ export class ServiceLocator { |
| 64 | 65 | ||
| 65 | static getFileReplicator() : FileReplicator { | 66 | static getFileReplicator() : FileReplicator { |
| 66 | if (!ServiceLocator.fileReplicator) { | 67 | if (!ServiceLocator.fileReplicator) { |
| 67 | - | ||
| 68 | ServiceLocator.fileReplicator = new FileReplicator(); | 68 | ServiceLocator.fileReplicator = new FileReplicator(); |
| 69 | - | ||
| 70 | ServiceLocator.fileReplicator.fileHandler = ServiceLocator.getFileHandler(); | 69 | ServiceLocator.fileReplicator.fileHandler = ServiceLocator.getFileHandler(); |
| 70 | + } | ||
| 71 | 71 | ||
| 72 | - if (ServiceLocator.getConfig().hasConfig(CONFIG_RETRY_TIMEOUT)) { | 72 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_RETRY_TIMEOUT)) { |
| 73 | - ServiceLocator.fileReplicator.retryTimeout = ServiceLocator.getConfig().getConfig(CONFIG_RETRY_TIMEOUT); | 73 | + ServiceLocator.fileReplicator.retryTimeout = ServiceLocator.getConfig().getConfig(CONFIG_RETRY_TIMEOUT); |
| 74 | - } | 74 | + } |
| 75 | - if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_KEY)) { | 75 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_KEY)) { |
| 76 | - ServiceLocator.fileReplicator.itemKey = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_KEY); | 76 | + ServiceLocator.fileReplicator.itemKey = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_KEY); |
| 77 | - } | 77 | + } |
| 78 | - if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_VALUE)) { | 78 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_VALUE)) { |
| 79 | - ServiceLocator.fileReplicator.itemValue = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_VALUE); | 79 | + ServiceLocator.fileReplicator.itemValue = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_VALUE); |
| 80 | - } | 80 | + } |
| 81 | - if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE)) { | 81 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE)) { |
| 82 | - ServiceLocator.fileReplicator.itemSourceAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE); | 82 | + ServiceLocator.fileReplicator.itemSourceAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE); |
| 83 | - } | 83 | + } |
| 84 | - if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_TARGET_ATTRIBUTE)) { | 84 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_TARGET_ATTRIBUTE)) { |
| 85 | - ServiceLocator.fileReplicator.itemTargetAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_TARGET_ATTRIBUTE); | 85 | + ServiceLocator.fileReplicator.itemTargetAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_TARGET_ATTRIBUTE); |
| 86 | - } | 86 | + } |
| 87 | - } | 87 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_TARGET_DIRECTORY)) { |
| 88 | - | 88 | + ServiceLocator.fileReplicator.targetDirectory = ServiceLocator.getConfig().getConfig(CONFIG_TARGET_DIRECTORY); |
| 89 | + } | ||
| 90 | + | ||
| 89 | return ServiceLocator.fileReplicator; | 91 | return ServiceLocator.fileReplicator; |
| 90 | } | 92 | } |
| 91 | 93 | ... | ... |
| 1 | +import { File } from './api/file'; | ||
| 2 | + | ||
| 1 | export class Util { | 3 | export class Util { |
| 2 | 4 | ||
| 3 | static getNameHash(path:string) { | 5 | static getNameHash(path:string) { |
| ... | @@ -7,4 +9,36 @@ export class Util { | ... | @@ -7,4 +9,36 @@ export class Util { |
| 7 | return "bsync_" + Math.abs(r); | 9 | return "bsync_" + Math.abs(r); |
| 8 | } | 10 | } |
| 9 | 11 | ||
| 12 | + /** | ||
| 13 | + * index >= 0, localFile is in files | ||
| 14 | + * index < 0, localFile is not in files | ||
| 15 | + */ | ||
| 16 | + static getFileIndex(files:Array<File>, localFile:string) : number { | ||
| 17 | + for (let i = 0; i < files.length; i++) { | ||
| 18 | + if (localFile == files[i].target) { | ||
| 19 | + return i; | ||
| 20 | + } | ||
| 21 | + } | ||
| 22 | + return -1; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + static getFilesForCleanup(files:Array<File>, localFiles:Array<string>) : Array<string> { | ||
| 26 | + let filesForCleanup = []; | ||
| 27 | + | ||
| 28 | + for (let localFile of localFiles) { | ||
| 29 | + let index = -1; | ||
| 30 | + | ||
| 31 | + index = Util.getFileIndex(files, localFile); | ||
| 32 | + | ||
| 33 | + if (index < 0) { | ||
| 34 | + filesForCleanup.push(localFile); | ||
| 35 | + } else { | ||
| 36 | + // splice for performance improvement only | ||
| 37 | + files.splice(index,1); | ||
| 38 | + } | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + return filesForCleanup; | ||
| 42 | + } | ||
| 43 | + | ||
| 10 | } | 44 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
-
Please register or login to post a comment