Stefan Huber

last update

...@@ -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
......
...@@ -8,10 +8,6 @@ export default { ...@@ -8,10 +8,6 @@ export default {
8 8
9 format: 'umd', 9 format: 'umd',
10 10
11 - globals: {
12 - 'rxjs' : 'Rx'
13 - },
14 -
15 plugins: [ 11 plugins: [
16 typescript() , 12 typescript() ,
17 globals(), 13 globals(),
......
...@@ -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) {
25 if (!handler) { 73 if (!handler) {
26 - subscriber.error("No handler for source: " + source); 74 + this.emit("error", "No handler for source: " + source);
27 - return; 75 + return this;
28 } 76 }
29 // file already exists and is not empty 77 // file already exists and is not empty
30 if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) { 78 if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) {
31 - subscriber.complete(); 79 + this.emit("complete");
32 - return; 80 + return this;
33 } 81 }
34 - var file = fs.createWriteStream(target, { 'flags': 'a' }); 82 + else {
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 });
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);
56 }); 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.
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 }); 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 + });
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();
77 }); 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 + });
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) => {
20 expect(progress).toBeGreaterThan(lastProgress); 55 expect(progress).toBeGreaterThan(lastProgress);
21 lastProgress = progress; 56 lastProgress = progress;
22 - } , 57 + }).once('error', (error:any) => {
23 - (error:any) => {
24 fail(); 58 fail();
25 - } , 59 + }).once('complete', () => {
26 - () => {
27 expect(lastProgress).toEqual(1); 60 expect(lastProgress).toEqual(1);
28 61
29 window['resolveLocalFileSystemURL'](target, (entry:any) => { 62 window['resolveLocalFileSystemURL'](target, (entry:any) => {
...@@ -31,9 +64,31 @@ exports.defineAutoTests = function() { ...@@ -31,9 +64,31 @@ exports.defineAutoTests = function() {
31 expect(entry.name).toEqual("full-moon.jpg"); 64 expect(entry.name).toEqual("full-moon.jpg");
32 done(); 65 done();
33 }); 66 });
34 - } 67 + });
35 - ); 68 + });
69 +
70 + it('should cleanup successfully', (done) => {
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>) => { 13 +
14 + download(source:string, target:string) {
14 let random = Math.random(); 15 let random = Math.random();
15 let error:boolean = random < TestFileHandler.errorRate; 16 let error:boolean = random < TestFileHandler.errorRate;
16 let counter = 1; 17 let counter = 1;
17 18
18 if (error) { 19 if (error) {
19 - subscriber.error("random error triggered"); 20 + setTimeout(() => {
20 - return; 21 + this.emit("error", "random error triggered");
21 - } 22 + },200);
22 - 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'; 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) => {
32 expect(progress).toBeGreaterThan(lastProgress); 35 expect(progress).toBeGreaterThan(lastProgress);
33 lastProgress = progress; 36 lastProgress = progress;
34 - } , 37 + })
35 - (error:any) => {} , 38 + .once('error', (error) => {})
36 - () => { 39 + .once('complete', () => {
37 expect(lastProgress).toEqual(1); 40 expect(lastProgress).toEqual(1);
38 expect(fs.existsSync(target)).toBeTruthy(); 41 expect(fs.existsSync(target)).toBeTruthy();
39 done(); 42 done();
40 - } 43 + })
41 - ); 44 + .download(source, target);
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 });
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);
66 }); 83 });
67 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 : [
10 { language: 'de', type : "asset", source : "http://someplace.com/icon.jpg" , target : "icon.jpg" } , 8 { language: 'de', type : "asset", source : "http://someplace.com/icon.jpg" , target : "icon.jpg" } ,
11 { language: 'de', type : "asset", source : "http://sampleuri.com/image.png" } , 9 { language: 'de', type : "asset", source : "http://sampleuri.com/image.png" } ,
12 { language: 'en', type : "asset", source : "https://secureasset.com/asset.mp3" , target : "music.mp3" } 10 { language: 'en', type : "asset", source : "https://secureasset.com/asset.mp3" , target : "music.mp3" }
13 - ] 11 + ];
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");
......
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 -
5 -export class CordovaDownloader implements FileHandler {
6 -
7 - download(source:string, target:string) : Rx.Observable<number> {
8 - return Rx.Observable.create((subscriber:Rx.Subscriber<number>) => {
9 -
10 - if (!window['FileTransfer']) {
11 - subscriber.error("Cordova FileTransfer object undefined");
12 - }
13 7
8 + triggerFileTransfer(source:string, target:string) {
14 let fileTransfer = new window['FileTransfer'](); 9 let fileTransfer = new window['FileTransfer']();
15 10
16 fileTransfer.onprogress = (progress:ProgressEvent) => { 11 fileTransfer.onprogress = (progress:ProgressEvent) => {
17 - subscriber.next(progress.loaded / progress.total); 12 + this.emit('progress', progress.loaded / progress.total);
18 }; 13 };
19 14
20 fileTransfer.download( 15 fileTransfer.download(
21 source , 16 source ,
22 target , 17 target ,
23 (entry:any) => { 18 (entry:any) => {
24 - subscriber.complete(); 19 + this.emit('complete', entry);
25 } , 20 } ,
26 (error:any) => { 21 (error:any) => {
27 - subscriber.error(error); 22 + this.emit('error', error);
28 }, 23 },
29 true 24 true
30 ); 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 + });
94 + }
31 95
32 }); 96 });
33 } 97 }
......
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>) => {
11 -
12 this.ipcRenderer.once('bsync-download-complete', () => { 12 this.ipcRenderer.once('bsync-download-complete', () => {
13 this.ipcRenderer.removeAllListeners('bsync-download-progress'); 13 this.ipcRenderer.removeAllListeners('bsync-download-progress');
14 this.ipcRenderer.removeAllListeners('bsync-download-error'); 14 this.ipcRenderer.removeAllListeners('bsync-download-error');
15 - subscriber.complete(); 15 + this.emit('complete');
16 }); 16 });
17 17
18 this.ipcRenderer.on('bsync-download-progress', (progress:number) => { 18 this.ipcRenderer.on('bsync-download-progress', (progress:number) => {
19 - subscriber.next(progress); 19 + this.emit('progress', progress);
20 }); 20 });
21 21
22 this.ipcRenderer.once('bsync-download-error', (error:any) => { 22 this.ipcRenderer.once('bsync-download-error', (error:any) => {
23 this.ipcRenderer.removeAllListeners('bsync-download-progress'); 23 this.ipcRenderer.removeAllListeners('bsync-download-progress');
24 this.ipcRenderer.removeAllListeners('bsync-download-complete'); 24 this.ipcRenderer.removeAllListeners('bsync-download-complete');
25 - subscriber.error(error); 25 + this.emit('error', error);
26 }); 26 });
27 27
28 this.ipcRenderer.send('bsync-download', { 28 this.ipcRenderer.send('bsync-download', {
29 source : source , 29 source : source ,
30 target : target 30 target : target
31 }); 31 });
32 +
33 + return this;
34 + }
35 +
36 + cleanup(files:Array<File>) : Promise<void> {
37 + return new Promise<void>((resolve, reject) => {
38 +
39 + this.ipcRenderer.once('bsync-cleanup-complete', () => {
40 + this.emit('cleanup-complete');
41 + resolve();
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 -
26 if (!handler) { 25 if (!handler) {
27 - subscriber.error("No handler for source: " + source); 26 + this.emit("error","No handler for source: " + source);
28 - return; 27 + return this;
29 } 28 }
30 29
31 // file already exists and is not empty 30 // file already exists and is not empty
32 if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) { 31 if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) {
33 - subscriber.complete(); 32 + this.emit("complete");
34 - return; 33 + return this;
35 - } 34 + } else {
36 -
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) => {
...@@ -47,23 +45,36 @@ export class NodeFileHandler implements FileHandler { ...@@ -47,23 +45,36 @@ export class NodeFileHandler implements FileHandler {
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
63 + return this;
64 + }
65 + }
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);
65 }); 73 });
74 + } catch (e) {
75 + }
66 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 + protected _retries: number = 10;
17 18
18 protected _itemKey = "type"; 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,18 +74,26 @@ export class FileReplicator extends EventEmitter { ...@@ -68,18 +74,26 @@ 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 }
...@@ -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,29 +150,47 @@ export class FileReplicator extends EventEmitter { ...@@ -136,29 +150,47 @@ 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
166 + if (this._fileHandler && this._files.length > 0) {
150 this.downloadFiles(this._files, this._fileHandler); 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);
159 setTimeout(() => { 188 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);
192 + } else {
193 + this.emit('error');
162 } 194 }
163 } 195 }
164 196
......
...@@ -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();
7 6
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 - );
15 }); 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');
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,10 +65,9 @@ export class ServiceLocator { ...@@ -64,10 +65,9 @@ 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);
...@@ -84,6 +84,8 @@ export class ServiceLocator { ...@@ -84,6 +84,8 @@ export class ServiceLocator {
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 + if (ServiceLocator.getConfig().hasConfig(CONFIG_TARGET_DIRECTORY)) {
88 + ServiceLocator.fileReplicator.targetDirectory = ServiceLocator.getConfig().getConfig(CONFIG_TARGET_DIRECTORY);
87 } 89 }
88 90
89 return ServiceLocator.fileReplicator; 91 return ServiceLocator.fileReplicator;
......
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
......