Showing
35 changed files
with
1508 additions
and
0 deletions
.gitignore
0 → 100644
README.md
0 → 100644
| 1 | +# bsync-client | ||
| 2 | + | ||
| 3 | +Allows downloading of files referenced by a http/https URI inside a couchdb/pouchdb record. The plugin works for cordova and electron projects. | ||
| 4 | + | ||
| 5 | +# Usage | ||
| 6 | + | ||
| 7 | +### In cordova projects | ||
| 8 | + | ||
| 9 | +### In electron projects | ||
| 10 | + | ||
| 11 | +Within the main process bsync needs to be integrated an initiated. | ||
| 12 | + | ||
| 13 | + import {Bsync} from 'bsync'; | ||
| 14 | + Bsync.init(ipcMain, filePath); | ||
| 15 | + |
dist/browser-build.js
0 → 100644
| 1 | +'use strict'; | ||
| 2 | + | ||
| 3 | +Object.defineProperty(exports, '__esModule', { value: true }); | ||
| 4 | + | ||
| 5 | +var events = require('events'); | ||
| 6 | +var rxjs = require('rxjs'); | ||
| 7 | + | ||
| 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 ElectronFileHandler = (function () { | ||
| 15 | + function ElectronFileHandler(ipcRenderer) { | ||
| 16 | + this.ipcRenderer = ipcRenderer; | ||
| 17 | + } | ||
| 18 | + ElectronFileHandler.prototype.download = function (source, target) { | ||
| 19 | + var _this = this; | ||
| 20 | + return rxjs.Observable.create(function (subscriber) { | ||
| 21 | + _this.ipcRenderer.once('bsync-download-complete', function () { | ||
| 22 | + _this.ipcRenderer.removeAllListeners('bsync-download-progress'); | ||
| 23 | + _this.ipcRenderer.removeAllListeners('bsync-download-error'); | ||
| 24 | + subscriber.complete(); | ||
| 25 | + }); | ||
| 26 | + _this.ipcRenderer.on('bsync-download-progress', function (progress) { | ||
| 27 | + subscriber.next(progress); | ||
| 28 | + }); | ||
| 29 | + _this.ipcRenderer.once('bsync-download-error', function (error) { | ||
| 30 | + _this.ipcRenderer.removeAllListeners('bsync-download-progress'); | ||
| 31 | + _this.ipcRenderer.removeAllListeners('bsync-download-complete'); | ||
| 32 | + subscriber.error(error); | ||
| 33 | + }); | ||
| 34 | + _this.ipcRenderer.send('bsync-download', { | ||
| 35 | + source: source, | ||
| 36 | + target: target | ||
| 37 | + }); | ||
| 38 | + }); | ||
| 39 | + }; | ||
| 40 | + return ElectronFileHandler; | ||
| 41 | +}()); | ||
| 42 | + | ||
| 43 | +var Util = (function () { | ||
| 44 | + function Util() { | ||
| 45 | + } | ||
| 46 | + Util.getNameHash = function (path) { | ||
| 47 | + for (var r = 0, i = 0; i < path.length; i++) { | ||
| 48 | + r = (r << 5) - r + path.charCodeAt(i), r &= r; | ||
| 49 | + } | ||
| 50 | + return "bsync_" + Math.abs(r); | ||
| 51 | + }; | ||
| 52 | + return Util; | ||
| 53 | +}()); | ||
| 54 | + | ||
| 55 | +var FileReplicator = (function (_super) { | ||
| 56 | + __extends(FileReplicator, _super); | ||
| 57 | + function FileReplicator() { | ||
| 58 | + _super.call(this); | ||
| 59 | + this._files = []; | ||
| 60 | + this._itemValidator = null; | ||
| 61 | + this._fileHandler = null; | ||
| 62 | + this._retryTimeout = 0; | ||
| 63 | + this._itemKey = "type"; | ||
| 64 | + this._itemValue = "asset"; | ||
| 65 | + this._itemSourceAttribute = "source"; | ||
| 66 | + this._itemTargetAttribute = "target"; | ||
| 67 | + } | ||
| 68 | + Object.defineProperty(FileReplicator.prototype, "files", { | ||
| 69 | + get: function () { | ||
| 70 | + return this._files; | ||
| 71 | + }, | ||
| 72 | + enumerable: true, | ||
| 73 | + configurable: true | ||
| 74 | + }); | ||
| 75 | + Object.defineProperty(FileReplicator.prototype, "fileHandler", { | ||
| 76 | + set: function (handler) { | ||
| 77 | + this._fileHandler = handler; | ||
| 78 | + }, | ||
| 79 | + enumerable: true, | ||
| 80 | + configurable: true | ||
| 81 | + }); | ||
| 82 | + Object.defineProperty(FileReplicator.prototype, "retryTimeout", { | ||
| 83 | + set: function (timeout) { | ||
| 84 | + this._retryTimeout = timeout; | ||
| 85 | + }, | ||
| 86 | + enumerable: true, | ||
| 87 | + configurable: true | ||
| 88 | + }); | ||
| 89 | + Object.defineProperty(FileReplicator.prototype, "itemValidator", { | ||
| 90 | + set: function (validator) { | ||
| 91 | + this._itemValidator = validator; | ||
| 92 | + }, | ||
| 93 | + enumerable: true, | ||
| 94 | + configurable: true | ||
| 95 | + }); | ||
| 96 | + Object.defineProperty(FileReplicator.prototype, "itemKey", { | ||
| 97 | + get: function () { | ||
| 98 | + return this._itemKey; | ||
| 99 | + }, | ||
| 100 | + set: function (key) { | ||
| 101 | + this._itemKey = key; | ||
| 102 | + }, | ||
| 103 | + enumerable: true, | ||
| 104 | + configurable: true | ||
| 105 | + }); | ||
| 106 | + Object.defineProperty(FileReplicator.prototype, "itemValue", { | ||
| 107 | + get: function () { | ||
| 108 | + return this._itemValue; | ||
| 109 | + }, | ||
| 110 | + set: function (value) { | ||
| 111 | + this._itemValue = value; | ||
| 112 | + }, | ||
| 113 | + enumerable: true, | ||
| 114 | + configurable: true | ||
| 115 | + }); | ||
| 116 | + Object.defineProperty(FileReplicator.prototype, "itemSourceAttribute", { | ||
| 117 | + get: function () { | ||
| 118 | + return this._itemSourceAttribute; | ||
| 119 | + }, | ||
| 120 | + set: function (sourceAttribute) { | ||
| 121 | + this._itemSourceAttribute = sourceAttribute; | ||
| 122 | + }, | ||
| 123 | + enumerable: true, | ||
| 124 | + configurable: true | ||
| 125 | + }); | ||
| 126 | + Object.defineProperty(FileReplicator.prototype, "itemTargetAttribute", { | ||
| 127 | + get: function () { | ||
| 128 | + return this._itemTargetAttribute; | ||
| 129 | + }, | ||
| 130 | + set: function (targetAttribute) { | ||
| 131 | + this._itemTargetAttribute = targetAttribute; | ||
| 132 | + }, | ||
| 133 | + enumerable: true, | ||
| 134 | + configurable: true | ||
| 135 | + }); | ||
| 136 | + FileReplicator.prototype.init = function (files) { | ||
| 137 | + if (files === void 0) { files = []; } | ||
| 138 | + this._files = files; | ||
| 139 | + }; | ||
| 140 | + /** | ||
| 141 | + * change from pouchdb replicate | ||
| 142 | + */ | ||
| 143 | + FileReplicator.prototype.pushChanges = function (change) { | ||
| 144 | + var items = []; | ||
| 145 | + if (change && change.docs && change.docs.length > 0) { | ||
| 146 | + for (var _i = 0, _a = change.docs; _i < _a.length; _i++) { | ||
| 147 | + var item = _a[_i]; | ||
| 148 | + if (item[this._itemKey] && item[this._itemKey] === this._itemValue) { | ||
| 149 | + items.push(item); | ||
| 150 | + } | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + var files = this.prepareFiles(items); | ||
| 154 | + for (var _b = 0, files_1 = files; _b < files_1.length; _b++) { | ||
| 155 | + var file = files_1[_b]; | ||
| 156 | + this._files.push(file); | ||
| 157 | + } | ||
| 158 | + }; | ||
| 159 | + FileReplicator.prototype.downloadFiles = function (files, fileHandler, index) { | ||
| 160 | + var _this = this; | ||
| 161 | + if (index === void 0) { index = 0; } | ||
| 162 | + if (index >= files.length) { | ||
| 163 | + return; | ||
| 164 | + } | ||
| 165 | + this.emit('start', { progress: 0, index: index, length: files.length }); | ||
| 166 | + fileHandler | ||
| 167 | + .download(files[index].source, files[index].target) | ||
| 168 | + .subscribe(function (progress) { | ||
| 169 | + _this.emit('progress', { progress: progress, index: index, length: files.length }); | ||
| 170 | + }, function (error) { | ||
| 171 | + _this.emit('error', { progress: 0, index: index, length: files.length, error: error }); | ||
| 172 | + }, function () { | ||
| 173 | + _this.emit('complete', { progress: 100, index: index, length: files.length }); | ||
| 174 | + _this.downloadFiles(files, fileHandler, index + 1); | ||
| 175 | + }); | ||
| 176 | + }; | ||
| 177 | + FileReplicator.prototype.prepareFiles = function (items) { | ||
| 178 | + var output = []; | ||
| 179 | + for (var _i = 0, items_1 = items; _i < items_1.length; _i++) { | ||
| 180 | + var item = items_1[_i]; | ||
| 181 | + if (item[this._itemSourceAttribute] && (!this._itemValidator || this._itemValidator(item))) { | ||
| 182 | + var file = { source: item[this._itemSourceAttribute], target: '' }; | ||
| 183 | + if (item[this._itemTargetAttribute]) { | ||
| 184 | + file.target = item[this._itemTargetAttribute]; | ||
| 185 | + } | ||
| 186 | + else { | ||
| 187 | + file.target = Util.getNameHash(file.source); | ||
| 188 | + } | ||
| 189 | + output.push(file); | ||
| 190 | + } | ||
| 191 | + } | ||
| 192 | + return output; | ||
| 193 | + }; | ||
| 194 | + FileReplicator.prototype.start = function () { | ||
| 195 | + var _this = this; | ||
| 196 | + this.on('complete', function (event) { | ||
| 197 | + if ((event.index + 1) >= event.length) { | ||
| 198 | + _this.replicationFinalized(event.index); | ||
| 199 | + } | ||
| 200 | + }); | ||
| 201 | + this.on('error', function (event) { | ||
| 202 | + _this.replicationFinalized(event.index); | ||
| 203 | + }); | ||
| 204 | + this.downloadFiles(this._files, this._fileHandler); | ||
| 205 | + }; | ||
| 206 | + FileReplicator.prototype.replicationFinalized = function (lastIndex) { | ||
| 207 | + var _this = this; | ||
| 208 | + if (lastIndex + 1 >= this._files.length) { | ||
| 209 | + this._files = []; | ||
| 210 | + this.emit('final'); | ||
| 211 | + } | ||
| 212 | + else if (this._retryTimeout > 0) { | ||
| 213 | + this._files.splice(0, lastIndex); | ||
| 214 | + setTimeout(function () { | ||
| 215 | + _this.downloadFiles(_this._files, _this._fileHandler); | ||
| 216 | + }, this._retryTimeout); | ||
| 217 | + } | ||
| 218 | + }; | ||
| 219 | + return FileReplicator; | ||
| 220 | +}(events.EventEmitter)); | ||
| 221 | + | ||
| 222 | +var CONFIG_ITEM_KEY = "itemKey"; | ||
| 223 | +var CONFIG_ITEM_VALUE = "itemValue"; | ||
| 224 | +var CONFIG_ITEM_SOURCE_ATTRIBUTE = "itemSourceAttribute"; | ||
| 225 | +var CONFIG_ITEM_TARGET_ATTRIBUTE = "itemTargetAttribute"; | ||
| 226 | + | ||
| 227 | +var CONFIG_RETRY_TIMEOUT = "retryTimeout"; | ||
| 228 | + | ||
| 229 | +var Config = (function () { | ||
| 230 | + function Config() { | ||
| 231 | + this.config = {}; | ||
| 232 | + } | ||
| 233 | + Config.prototype.hasConfig = function (key) { | ||
| 234 | + if (this.config[key]) { | ||
| 235 | + return true; | ||
| 236 | + } | ||
| 237 | + return false; | ||
| 238 | + }; | ||
| 239 | + Config.prototype.getConfig = function (key) { | ||
| 240 | + return this.config[key]; | ||
| 241 | + }; | ||
| 242 | + Config.prototype.setConfig = function (key, value) { | ||
| 243 | + this.config[key] = value; | ||
| 244 | + }; | ||
| 245 | + return Config; | ||
| 246 | +}()); | ||
| 247 | + | ||
| 248 | +var ENV_ELECTRON = "electron"; | ||
| 249 | +var ENV_CORDOVA = "cordova"; | ||
| 250 | +var ENV_UNKNOWN = "unknown"; | ||
| 251 | +var ServiceLocator = (function () { | ||
| 252 | + function ServiceLocator() { | ||
| 253 | + } | ||
| 254 | + ServiceLocator.addFileHandler = function (environment, fileHandler) { | ||
| 255 | + ServiceLocator.fileHandlers[environment] = fileHandler; | ||
| 256 | + }; | ||
| 257 | + ServiceLocator.getConfig = function () { | ||
| 258 | + if (!ServiceLocator.config) { | ||
| 259 | + ServiceLocator.config = new Config(); | ||
| 260 | + } | ||
| 261 | + return ServiceLocator.config; | ||
| 262 | + }; | ||
| 263 | + ServiceLocator.getEnvironment = function () { | ||
| 264 | + if (typeof window['require'] === 'function' && window['require']('electron')) { | ||
| 265 | + return ENV_ELECTRON; | ||
| 266 | + } | ||
| 267 | + if (typeof window['FileTransfer'] === 'function') { | ||
| 268 | + return ENV_CORDOVA; | ||
| 269 | + } | ||
| 270 | + return ENV_UNKNOWN; | ||
| 271 | + }; | ||
| 272 | + ServiceLocator.getFileHandler = function () { | ||
| 273 | + var environment = ServiceLocator.getEnvironment(); | ||
| 274 | + if (ServiceLocator.fileHandlers[environment]) { | ||
| 275 | + return ServiceLocator.fileHandlers[environment]; | ||
| 276 | + } | ||
| 277 | + if (environment === ENV_ELECTRON) { | ||
| 278 | + return new ElectronFileHandler(window['require']('electron').ipcRenderer); | ||
| 279 | + } | ||
| 280 | + return null; | ||
| 281 | + }; | ||
| 282 | + ServiceLocator.getFileReplicator = function () { | ||
| 283 | + if (!ServiceLocator.fileReplicator) { | ||
| 284 | + ServiceLocator.fileReplicator = new FileReplicator(); | ||
| 285 | + ServiceLocator.fileReplicator.fileHandler = ServiceLocator.getFileHandler(); | ||
| 286 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_RETRY_TIMEOUT)) { | ||
| 287 | + ServiceLocator.fileReplicator.retryTimeout = ServiceLocator.getConfig().getConfig(CONFIG_RETRY_TIMEOUT); | ||
| 288 | + } | ||
| 289 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_KEY)) { | ||
| 290 | + ServiceLocator.fileReplicator.itemKey = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_KEY); | ||
| 291 | + } | ||
| 292 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_VALUE)) { | ||
| 293 | + ServiceLocator.fileReplicator.itemValue = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_VALUE); | ||
| 294 | + } | ||
| 295 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE)) { | ||
| 296 | + ServiceLocator.fileReplicator.itemSourceAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE); | ||
| 297 | + } | ||
| 298 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_TARGET_ATTRIBUTE)) { | ||
| 299 | + ServiceLocator.fileReplicator.itemTargetAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_TARGET_ATTRIBUTE); | ||
| 300 | + } | ||
| 301 | + } | ||
| 302 | + return ServiceLocator.fileReplicator; | ||
| 303 | + }; | ||
| 304 | + ServiceLocator.fileHandlers = {}; | ||
| 305 | + return ServiceLocator; | ||
| 306 | +}()); | ||
| 307 | + | ||
| 308 | +function loadBsyncPlugin(PouchDB) { | ||
| 309 | + var pouchReplicate = PouchDB.replicate; | ||
| 310 | + PouchDB.plugin(function (PouchDB) { | ||
| 311 | + PouchDB.replicate = function () { | ||
| 312 | + var eventEmitter = new events.EventEmitter(); | ||
| 313 | + var emitter = pouchReplicate.apply(this, arguments); | ||
| 314 | + var replicator = ServiceLocator.getFileReplicator(); | ||
| 315 | + var db = arguments[1]; | ||
| 316 | + replicator.once('final', function (event) { | ||
| 317 | + eventEmitter.emit('complete'); | ||
| 318 | + eventEmitter.removeAllListeners(); | ||
| 319 | + }); | ||
| 320 | + replicator.on('error', function (event) { | ||
| 321 | + eventEmitter.emit('file-replicator-error', event); | ||
| 322 | + }); | ||
| 323 | + replicator.on('complete', function (event) { | ||
| 324 | + eventEmitter.emit('file-replicator-complete', event); | ||
| 325 | + }); | ||
| 326 | + replicator.on('progress', function (event) { | ||
| 327 | + eventEmitter.emit('file-replicator-progress', event); | ||
| 328 | + }); | ||
| 329 | + emitter.once('change', function (info) { | ||
| 330 | + eventEmitter.emit('change', info); | ||
| 331 | + }); | ||
| 332 | + emitter.once('complete', function (info) { | ||
| 333 | + db.query('index_type/type', { | ||
| 334 | + include_docs: true, | ||
| 335 | + key: replicator.itemValue | ||
| 336 | + }).then(function (res) { | ||
| 337 | + var docs = { docs: [] }; | ||
| 338 | + for (var _i = 0, _a = res.rows; _i < _a.length; _i++) { | ||
| 339 | + var r = _a[_i]; | ||
| 340 | + docs.docs.push(r.doc); | ||
| 341 | + } | ||
| 342 | + replicator.pushChanges(docs); | ||
| 343 | + replicator.start(); | ||
| 344 | + }).catch(function (error) { | ||
| 345 | + eventEmitter.emit('error', error); | ||
| 346 | + }); | ||
| 347 | + }); | ||
| 348 | + emitter.once('error', function (error) { | ||
| 349 | + eventEmitter.emit('error', error); | ||
| 350 | + }); | ||
| 351 | + return eventEmitter; | ||
| 352 | + }; | ||
| 353 | + }); | ||
| 354 | +} | ||
| 355 | + | ||
| 356 | +if (typeof window !== 'undefined' && window['PouchDB']) { | ||
| 357 | + loadBsyncPlugin(window['PouchDB']); | ||
| 358 | +} | ||
| 359 | + | ||
| 360 | +exports.loadBsyncPlugin = loadBsyncPlugin; | ||
| 361 | +exports.ENV_ELECTRON = ENV_ELECTRON; | ||
| 362 | +exports.ENV_CORDOVA = ENV_CORDOVA; | ||
| 363 | +exports.ENV_UNKNOWN = ENV_UNKNOWN; | ||
| 364 | +exports.ServiceLocator = ServiceLocator; | ||
| 365 | +//# sourceMappingURL=browser-build.js.map |
dist/browser-build.js.map
0 → 100644
| 1 | +{"version":3,"file":null,"sources":["../src/file-handler/electron-file-handler.ts","../src/util.ts","../src/file-replicator.ts","../src/config.ts","../src/service-locator.ts","../src/browser-main.ts"],"sourcesContent":["import { Observable, Subscriber } from 'rxjs';\nimport { FileHandler } from '../api/file-handler';\n\nexport class ElectronFileHandler implements FileHandler {\n\n constructor (private ipcRenderer:any) {\n }\n\n download(source:string, target:string) : Observable<number> {\n return Observable.create((subscriber:Subscriber<number>) => {\n\n this.ipcRenderer.once('bsync-download-complete', () => {\n this.ipcRenderer.removeAllListeners('bsync-download-progress');\n this.ipcRenderer.removeAllListeners('bsync-download-error');\n subscriber.complete();\n });\n\n this.ipcRenderer.on('bsync-download-progress', (progress:number) => {\n subscriber.next(progress);\n });\n\n this.ipcRenderer.once('bsync-download-error', (error:any) => {\n this.ipcRenderer.removeAllListeners('bsync-download-progress');\n this.ipcRenderer.removeAllListeners('bsync-download-complete');\n subscriber.error(error);\n });\n\n this.ipcRenderer.send('bsync-download', {\n source : source ,\n target : target\n });\n });\n }\n\n}","export class Util {\n\n static getNameHash(path:string) {\n for(var r=0,i=0;i<path.length;i++) {\n r=(r<<5)-r+path.charCodeAt(i),r&=r;\n }\n return \"bsync_\" + Math.abs(r);\n }\n\n}","import {FileHandler} from './api/file-handler';\nimport {File} from './api/file';\nimport {Util} from './util';\nimport {EventEmitter} from 'events';\n\nexport class FileReplicator extends EventEmitter {\n\n constructor() {\n super();\n }\n\n protected _files:Array<File> = [];\n\n protected _itemValidator: (item:any) => boolean = null; \n protected _fileHandler:FileHandler = null; \n protected _retryTimeout:number = 0;\n \n protected _itemKey = \"type\";\n protected _itemValue = \"asset\";\n protected _itemSourceAttribute = \"source\";\n protected _itemTargetAttribute = \"target\";\n\n get files(): Array<File> {\n return this._files;\n }\n\n set fileHandler (handler:FileHandler) {\n this._fileHandler = handler;\n }\n\n set retryTimeout (timeout:number) {\n this._retryTimeout = timeout;\n }\n\n set itemValidator(validator:(item:any) => boolean) {\n this._itemValidator = validator;\n }\n\n set itemKey(key:string) {\n this._itemKey = key;\n }\n\n set itemValue(value:string) {\n this._itemValue = value;\n }\n\n set itemSourceAttribute(sourceAttribute:string) {\n this._itemSourceAttribute = sourceAttribute;\n }\n\n set itemTargetAttribute(targetAttribute:string) {\n this._itemTargetAttribute = targetAttribute;\n }\n\n get itemKey() {\n return this._itemKey;\n }\n\n get itemValue() {\n return this._itemValue;\n }\n\n get itemSourceAttribute() {\n return this._itemSourceAttribute;\n }\n\n get itemTargetAttribute() {\n return this._itemTargetAttribute;\n }\n\n init(files: Array<File> = []) {\n this._files = files;\n }\n\n /**\n * change from pouchdb replicate\n */\n pushChanges(change:any) {\n let items:Array<any> = [];\n\n if (change && change.docs && change.docs.length > 0) {\n for (let item of change.docs) {\n if (item[this._itemKey] && item[this._itemKey] === this._itemValue) {\n items.push(item);\n }\n }\n }\n \n let files = this.prepareFiles(items);\n\n for (let file of files) {\n this._files.push(file);\n }\n }\n\n downloadFiles(files:Array<File>, fileHandler:FileHandler, index:number = 0) { \n if (index >= files.length) {\n return;\n }\n\n this.emit('start', { progress: 0, index : index, length : files.length });\n\n fileHandler\n .download(files[index].source, files[index].target)\n .subscribe(\n progress => {\n this.emit('progress', { progress : progress, index : index, length : files.length })\n } ,\n error => {\n this.emit('error', { progress : 0, index : index, length : files.length, error: error });\n } ,\n () => {\n this.emit('complete', { progress : 100 , index : index, length : files.length });\n this.downloadFiles(files, fileHandler, index+1);\n }\n );\n }\n\n prepareFiles(items: Array<any>) : Array<File> {\n let output = [];\n\n for (let item of items) {\n if (item[this._itemSourceAttribute] && (!this._itemValidator || this._itemValidator(item))) {\n let file = { source : item[this._itemSourceAttribute] , target : '' };\n\n if (item[this._itemTargetAttribute]) {\n file.target = item[this._itemTargetAttribute];\n } else {\n file.target = Util.getNameHash(file.source);\n }\n\n output.push(file);\n }\n }\n\n return output;\n }\n\n start() {\n this.on('complete', (event:any) => {\n if ((event.index + 1) >= event.length) {\n this.replicationFinalized(event.index);\n }\n });\n\n this.on('error', (event:any) => {\n this.replicationFinalized(event.index);\n }); \n\n this.downloadFiles(this._files, this._fileHandler);\n }\n\n replicationFinalized(lastIndex:number) { \n if (lastIndex+1 >= this._files.length) { // all finished\n this._files = [];\n this.emit('final');\n } else if (this._retryTimeout > 0) { // restart after last success \n this._files.splice(0,lastIndex);\n setTimeout(() => { \n this.downloadFiles(this._files, this._fileHandler);\n }, this._retryTimeout);\n } \n }\n\n}","export const CONFIG_ITEM_KEY = \"itemKey\";\nexport const CONFIG_ITEM_VALUE = \"itemValue\";\nexport const CONFIG_ITEM_SOURCE_ATTRIBUTE = \"itemSourceAttribute\";\nexport const CONFIG_ITEM_TARGET_ATTRIBUTE = \"itemTargetAttribute\";\nexport const CONFIG_ITEM_VALIDATOR = \"itemValidator\";\nexport const CONFIG_RETRY_TIMEOUT = \"retryTimeout\";\nexport const CONFIG_FILE_HANDLER = \"fileHandler\";\n\nexport class Config {\n\n protected config:any = {};\n\n hasConfig(key:string) {\n if (this.config[key]) {\n return true;\n }\n return false;\n }\n\n getConfig(key:string) {\n return this.config[key];\n }\n\n setConfig(key:string, value:any) {\n this.config[key] = value;\n }\n\n}","import {FileHandler} from './api/file-handler';\nimport {ElectronFileHandler} from './file-handler/electron-file-handler';\nimport {FileReplicator} from './file-replicator';\nimport {\n Config,\n CONFIG_RETRY_TIMEOUT,\n CONFIG_ITEM_KEY,\n CONFIG_ITEM_VALUE,\n CONFIG_ITEM_TARGET_ATTRIBUTE,\n CONFIG_ITEM_SOURCE_ATTRIBUTE\n} from './config';\n\nexport const ENV_ELECTRON = \"electron\";\nexport const ENV_CORDOVA = \"cordova\";\nexport const ENV_UNKNOWN = \"unknown\";\n\nexport class ServiceLocator {\n\n protected static fileHandlers:any = {};\n protected static fileReplicator: FileReplicator;\n protected static config: Config;\n\n static addFileHandler(environment:string, fileHandler:FileHandler) {\n ServiceLocator.fileHandlers[environment] = fileHandler;\n }\n\n static getConfig() : Config {\n if (!ServiceLocator.config) {\n ServiceLocator.config = new Config();\n }\n\n return ServiceLocator.config;\n }\n\n static getEnvironment() {\n if (typeof window['require'] === 'function' && window['require']('electron')) {\n return ENV_ELECTRON;\n }\n if (typeof window['FileTransfer'] === 'function') {\n return ENV_CORDOVA;\n }\n\n return ENV_UNKNOWN;\n }\n\n static getFileHandler() : FileHandler {\n let environment = ServiceLocator.getEnvironment();\n\n if (ServiceLocator.fileHandlers[environment]) {\n return ServiceLocator.fileHandlers[environment];\n }\n\n if (environment === ENV_ELECTRON) {\n return new ElectronFileHandler(window['require']('electron').ipcRenderer);\n }\n \n return null;\n }\n\n static getFileReplicator() : FileReplicator {\n if (!ServiceLocator.fileReplicator) {\n\n ServiceLocator.fileReplicator = new FileReplicator();\n\n ServiceLocator.fileReplicator.fileHandler = ServiceLocator.getFileHandler();\n\n if (ServiceLocator.getConfig().hasConfig(CONFIG_RETRY_TIMEOUT)) {\n ServiceLocator.fileReplicator.retryTimeout = ServiceLocator.getConfig().getConfig(CONFIG_RETRY_TIMEOUT);\n }\n if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_KEY)) {\n ServiceLocator.fileReplicator.itemKey = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_KEY);\n }\n if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_VALUE)) {\n ServiceLocator.fileReplicator.itemValue = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_VALUE);\n }\n if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE)) {\n ServiceLocator.fileReplicator.itemSourceAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE);\n }\n if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_TARGET_ATTRIBUTE)) {\n ServiceLocator.fileReplicator.itemTargetAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_TARGET_ATTRIBUTE);\n }\n } \n\n return ServiceLocator.fileReplicator;\n }\n\n}","import { EventEmitter } from 'events';\nimport { ServiceLocator } from './service-locator';\n\nexport * from './service-locator';\n\nexport function loadBsyncPlugin (PouchDB) {\n let pouchReplicate = PouchDB.replicate; \n\n PouchDB.plugin((PouchDB) => {\n PouchDB.replicate = function() {\n let eventEmitter = new EventEmitter();\n let emitter = pouchReplicate.apply(this, arguments);\n let replicator = ServiceLocator.getFileReplicator();\n let db = arguments[1]; \n \n replicator.once('final', event => { \n eventEmitter.emit('complete');\n eventEmitter.removeAllListeners();\n });\n\n replicator.on('error', event => {\n eventEmitter.emit('file-replicator-error', event);\n });\n\n replicator.on('complete', event => {\n eventEmitter.emit('file-replicator-complete', event);\n });\n\n replicator.on('progress', event => {\n eventEmitter.emit('file-replicator-progress', event);\n });\n \n emitter.once('change', info => { \n eventEmitter.emit('change', info);\n });\n\n emitter.once('complete', info => { \n db.query('index_type/type',{\n include_docs : true,\n key : replicator.itemValue\n }).then((res) => { \n let docs = { docs : [] }; \n for (let r of res.rows) {\n docs.docs.push(r.doc);\n }\n\n replicator.pushChanges(docs);\n replicator.start();\n }).catch(error => {\n eventEmitter.emit('error', error);\n });\n });\n\n emitter.once('error', (error) => {\n eventEmitter.emit('error', error);\n });\n\n return eventEmitter;\n };\n });\n};\n\nif (typeof window !== 'undefined' && window['PouchDB']) {\n loadBsyncPlugin(window['PouchDB']);\n}\n"],"names":["Observable","EventEmitter"],"mappings":"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"} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
dist/node-build.js
0 → 100644
| 1 | +'use strict'; | ||
| 2 | + | ||
| 3 | +var rxjs = require('rxjs'); | ||
| 4 | +var http = require('http'); | ||
| 5 | +var https = require('https'); | ||
| 6 | +var fs = require('fs'); | ||
| 7 | + | ||
| 8 | +var NodeFileHandler = (function () { | ||
| 9 | + function NodeFileHandler() { | ||
| 10 | + } | ||
| 11 | + NodeFileHandler.prototype.selectProtocol = function (url) { | ||
| 12 | + if (url.search(/^http:\/\//) === 0) { | ||
| 13 | + return http; | ||
| 14 | + } | ||
| 15 | + else if (url.search(/^https:\/\//) === 0) { | ||
| 16 | + return https; | ||
| 17 | + } | ||
| 18 | + else { | ||
| 19 | + return null; | ||
| 20 | + } | ||
| 21 | + }; | ||
| 22 | + NodeFileHandler.prototype.download = function (source, target) { | ||
| 23 | + var handler = this.selectProtocol(source); | ||
| 24 | + return rxjs.Observable.create(function (subscriber) { | ||
| 25 | + if (!handler) { | ||
| 26 | + subscriber.error("No handler for source: " + source); | ||
| 27 | + return; | ||
| 28 | + } | ||
| 29 | + // file already exists and is not empty | ||
| 30 | + if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) { | ||
| 31 | + subscriber.complete(); | ||
| 32 | + return; | ||
| 33 | + } | ||
| 34 | + var file = fs.createWriteStream(target, { 'flags': 'a' }); | ||
| 35 | + handler.get(source, function (response) { | ||
| 36 | + var size = response.headers['content-length']; // in bytes | ||
| 37 | + var prog = 0; // already downloaded | ||
| 38 | + var progCounts = 100; // how many progress events should be triggerd (1-100 %) | ||
| 39 | + var nextProg = (1 / progCounts); | ||
| 40 | + response.on('data', function (chunk) { | ||
| 41 | + prog += chunk.length; | ||
| 42 | + file.write(chunk, 'binary'); | ||
| 43 | + if ((prog / size) > nextProg) { | ||
| 44 | + subscriber.next(prog / size); | ||
| 45 | + nextProg += (1 / progCounts); | ||
| 46 | + } | ||
| 47 | + }); | ||
| 48 | + response.on('end', function () { | ||
| 49 | + file.end(); | ||
| 50 | + subscriber.complete(); | ||
| 51 | + }); | ||
| 52 | + }).on('error', function (error) { | ||
| 53 | + fs.unlink(target); | ||
| 54 | + subscriber.error("Error while downloading: " + error); | ||
| 55 | + }); | ||
| 56 | + }); | ||
| 57 | + }; | ||
| 58 | + return NodeFileHandler; | ||
| 59 | +}()); | ||
| 60 | + | ||
| 61 | +var Bsync = (function () { | ||
| 62 | + function Bsync() { | ||
| 63 | + } | ||
| 64 | + Bsync.configIpcMain = function (ipcMain, downloadDir) { | ||
| 65 | + var nodeFileHander = new NodeFileHandler(); | ||
| 66 | + ipcMain.on('bsync-download', function (event, args) { | ||
| 67 | + nodeFileHander.download(args.source, downloadDir + args.target) | ||
| 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'); }); | ||
| 69 | + }); | ||
| 70 | + }; | ||
| 71 | + return Bsync; | ||
| 72 | +}()); | ||
| 73 | + | ||
| 74 | +module.exports = Bsync; | ||
| 75 | +//# sourceMappingURL=node-build.js.map |
dist/node-build.js.map
0 → 100644
| 1 | +{"version":3,"file":null,"sources":["../src/file-handler/node-file-handler.ts","../src/node-main.ts"],"sourcesContent":["import { Observable, Subscriber } from 'rxjs';\nimport { FileHandler } from '../api/file-handler';\nimport * as http from 'http';\nimport * as https from 'https';\nimport * as fs from 'fs';\n\nexport class NodeFileHandler implements FileHandler {\n\n selectProtocol(url:string) : any {\n if (url.search(/^http:\\/\\//) === 0) {\n return http;\n } else if (url.search(/^https:\\/\\//) === 0) {\n return https;\n } else {\n return null;\n }\n }\n\n download(source:string, target:string) : Observable<number> {\n\n let handler = this.selectProtocol(source);\n\n return Observable.create((subscriber:Subscriber<number>) => {\n \n if (!handler) {\n subscriber.error(\"No handler for source: \" + source);\n return;\n }\n\n // file already exists and is not empty\n if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) {\n subscriber.complete();\n return;\n }\n\n let file = fs.createWriteStream(target, {'flags': 'a'});\n\n handler.get(source, (response) => {\n let size = response.headers['content-length']; // in bytes\n let prog = 0; // already downloaded\n let progCounts = 100; // how many progress events should be triggerd (1-100 %)\n let nextProg = (1/progCounts);\n \n response.on('data', (chunk) => {\n prog += chunk.length;\n file.write(chunk, 'binary');\n\n if ((prog / size) > nextProg) {\n subscriber.next(prog / size);\n nextProg += (1 / progCounts);\n } \n });\n\n response.on('end', () => {\n file.end();\n subscriber.complete();\n });\n \n }).on('error', (error) => {\n fs.unlink(target);\n subscriber.error(\"Error while downloading: \" + error);\n });\n\n });\n\n }\n\n}","import { NodeFileHandler } from './file-handler/node-file-handler';\n\nexport default class Bsync {\n\n static configIpcMain(ipcMain: any, downloadDir:string) {\n let nodeFileHander = new NodeFileHandler();\n\n ipcMain.on('bsync-download', (event, args) => {\n nodeFileHander.download(args.source, downloadDir + args.target)\n .subscribe(\n (progress:number) => { event.sender.send('bsync-download-progress', progress); } ,\n (error:any) => { event.sender.send('bsync-download-error', error); } ,\n () => { event.sender.send('bsync-download-complete'); }\n );\n });\n }\n\n}"],"names":["Observable","fs.existsSync","fs.statSync","fs.createWriteStream","fs.unlink"],"mappings":"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"} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
karma.conf.js
0 → 100644
| 1 | +// Karma configuration | ||
| 2 | +// Generated on Tue Jan 03 2017 13:04:16 GMT+0100 (CET) | ||
| 3 | + | ||
| 4 | +module.exports = function(config) { | ||
| 5 | + config.set({ | ||
| 6 | + | ||
| 7 | + // base path that will be used to resolve all patterns (eg. files, exclude) | ||
| 8 | + basePath: '', | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + // frameworks to use | ||
| 12 | + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter | ||
| 13 | + frameworks: ['jasmine'], | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + // list of files / patterns to load in the browser | ||
| 17 | + files: [ | ||
| 18 | + './node_modules/rxjs/bundles/Rx.min.js' , | ||
| 19 | + './node_modules/pouchdb/dist/pouchdb.min.js' , | ||
| 20 | + './.tmp/browser-test.js' | ||
| 21 | + ], | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + // list of files to exclude | ||
| 25 | + exclude: [ | ||
| 26 | + ], | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + // preprocess matching files before serving them to the browser | ||
| 30 | + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor | ||
| 31 | + preprocessors: { | ||
| 32 | + }, | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + // test results reporter to use | ||
| 36 | + // possible values: 'dots', 'progress' | ||
| 37 | + // available reporters: https://npmjs.org/browse/keyword/karma-reporter | ||
| 38 | + reporters: ['progress'], | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + // web server port | ||
| 42 | + port: 9876, | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + // enable / disable colors in the output (reporters and logs) | ||
| 46 | + colors: true, | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + // level of logging | ||
| 50 | + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG | ||
| 51 | + logLevel: config.LOG_INFO, | ||
| 52 | + | ||
| 53 | + | ||
| 54 | + // enable / disable watching file and executing tests whenever any file changes | ||
| 55 | + autoWatch: false, | ||
| 56 | + | ||
| 57 | + | ||
| 58 | + // start these browsers | ||
| 59 | + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher | ||
| 60 | + browsers: ['Chrome'], | ||
| 61 | + | ||
| 62 | + | ||
| 63 | + // Continuous Integration mode | ||
| 64 | + // if true, Karma captures browsers, runs the tests and exits | ||
| 65 | + singleRun: true, | ||
| 66 | + | ||
| 67 | + // Concurrency level | ||
| 68 | + // how many browser should be started simultaneous | ||
| 69 | + concurrency: Infinity | ||
| 70 | + }) | ||
| 71 | +} |
package.json
0 → 100644
| 1 | +{ | ||
| 2 | + "name": "bsync-client", | ||
| 3 | + "version": "1.0.0", | ||
| 4 | + "description": "", | ||
| 5 | + "main": "dist/browser-build.js", | ||
| 6 | + "scripts": { | ||
| 7 | + "build": "npm run build:node && npm run build:browser", | ||
| 8 | + "build:node": "rollup -c ./rollup.config.node.js", | ||
| 9 | + "build:browser": "rollup --config ./rollup.config.browser.js", | ||
| 10 | + "pretest": "scripts/before-test.sh", | ||
| 11 | + "posttest": "scripts/after-test.sh", | ||
| 12 | + "test": "npm run test:node && npm run test:browser", | ||
| 13 | + "test:node": "rollup --config ./rollup.config.node-test.js && jasmine", | ||
| 14 | + "test:browser": "rollup --config ./rollup.config.browser-test.js && karma start", | ||
| 15 | + "test:cordova": "npm run build:browser && scripts/prepare-cordova-test.sh" | ||
| 16 | + }, | ||
| 17 | + "author": "", | ||
| 18 | + "license": "ISC", | ||
| 19 | + "dependencies": { | ||
| 20 | + "rxjs": "^5.0.2" | ||
| 21 | + }, | ||
| 22 | + "devDependencies": { | ||
| 23 | + "@types/jasmine": "^2.5.40", | ||
| 24 | + "cordova": "^6.4.0", | ||
| 25 | + "jasmine": "^2.5.2", | ||
| 26 | + "karma": "^1.3.0", | ||
| 27 | + "karma-chrome-launcher": "^2.0.0", | ||
| 28 | + "karma-jasmine": "^1.1.0", | ||
| 29 | + "pouchdb": "^6.1.0", | ||
| 30 | + "pouchdb-upsert": "^2.0.2", | ||
| 31 | + "rollup": "^0.39.2", | ||
| 32 | + "rollup-plugin-commonjs": "^7.0.0", | ||
| 33 | + "rollup-plugin-ignore": "^1.0.3", | ||
| 34 | + "rollup-plugin-node-builtins": "^2.0.0", | ||
| 35 | + "rollup-plugin-node-globals": "^1.1.0", | ||
| 36 | + "rollup-plugin-node-resolve": "^2.0.0", | ||
| 37 | + "rollup-plugin-typescript": "^0.8.1" | ||
| 38 | + } | ||
| 39 | +} |
plugin.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" | ||
| 3 | + id="bsync-client" | ||
| 4 | + version="1.0.0" | ||
| 5 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
| 6 | + | ||
| 7 | + <name>bsync client plugin</name> | ||
| 8 | + <description>Cordova plugin for syncing files referenced by pouchdb records</description> | ||
| 9 | + <author>Stefan Huber</author> | ||
| 10 | + <keywords>pouchdb, file sync</keywords> | ||
| 11 | + <license>Apache 2.0 License</license> | ||
| 12 | + | ||
| 13 | + <asset src="dist/browser-build.js" target="bsync.js"></asset> | ||
| 14 | + | ||
| 15 | + <dependency id="cordova-plugin-file-transfer" url="https://github.com/apache/cordova-plugin-file-transfer" commit="master" /> | ||
| 16 | + | ||
| 17 | +</plugin> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
rollup.config.browser-test.js
0 → 100644
| 1 | +import typescript from 'rollup-plugin-typescript'; | ||
| 2 | +import globals from 'rollup-plugin-node-globals'; | ||
| 3 | +import builtins from 'rollup-plugin-node-builtins'; | ||
| 4 | + | ||
| 5 | +export default { | ||
| 6 | + entry: './spec/browser-test.ts', | ||
| 7 | + dest: './.tmp/browser-test.js', | ||
| 8 | + | ||
| 9 | + format: 'umd', | ||
| 10 | + | ||
| 11 | + globals: { | ||
| 12 | + 'rxjs' : 'Rx' | ||
| 13 | + }, | ||
| 14 | + | ||
| 15 | + plugins: [ | ||
| 16 | + typescript() , | ||
| 17 | + globals(), | ||
| 18 | + builtins() | ||
| 19 | + ] | ||
| 20 | +}; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
rollup.config.browser.js
0 → 100644
| 1 | +import typescript from 'rollup-plugin-typescript'; | ||
| 2 | +import builtins from 'rollup-plugin-node-builtins'; | ||
| 3 | +import globals from 'rollup-plugin-node-globals'; | ||
| 4 | + | ||
| 5 | +export default { | ||
| 6 | + | ||
| 7 | + moduleName : 'bsync', | ||
| 8 | + | ||
| 9 | + entry: './src/browser-main.ts', | ||
| 10 | + dest: './dist/browser-build.js', | ||
| 11 | + | ||
| 12 | + format: 'cjs', | ||
| 13 | + | ||
| 14 | + sourceMap: true , | ||
| 15 | + | ||
| 16 | + plugins: [ | ||
| 17 | + typescript(), | ||
| 18 | + // globals(), | ||
| 19 | + // builtins() | ||
| 20 | + ] | ||
| 21 | +}; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
rollup.config.node-test.js
0 → 100644
rollup.config.node.js
0 → 100644
| 1 | +import typescript from 'rollup-plugin-typescript'; | ||
| 2 | + | ||
| 3 | +export default { | ||
| 4 | + entry: './src/node-main.ts', | ||
| 5 | + dest: './dist/node-build.js', | ||
| 6 | + | ||
| 7 | + format: 'cjs', | ||
| 8 | + | ||
| 9 | + sourceMap: true , | ||
| 10 | + | ||
| 11 | + plugins: [ | ||
| 12 | + typescript() | ||
| 13 | + ] | ||
| 14 | +}; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
scripts/after-test.sh
0 → 100755
scripts/before-test.sh
0 → 100755
scripts/prepare-cordova-test.sh
0 → 100755
| 1 | +#!/bin/bash | ||
| 2 | + | ||
| 3 | +COMMAND=${1:-emulate} | ||
| 4 | +PLATFORM=${2:-android} | ||
| 5 | + | ||
| 6 | +echo "cordova $COMMAND $PLATFORM" | ||
| 7 | + | ||
| 8 | +cd .. | ||
| 9 | +rm -r ./bysnc-client-test-app | ||
| 10 | +./bsync-client/node_modules/.bin/cordova create bysnc-client-test-app | ||
| 11 | +cd ./bysnc-client-test-app | ||
| 12 | + | ||
| 13 | +../bsync-client/node_modules/.bin/cordova platform add $PLATFORM | ||
| 14 | +../bsync-client/node_modules/.bin/cordova plugin add ../bsync-client | ||
| 15 | +../bsync-client/node_modules/.bin/cordova plugin add ../bsync-client/spec/cordova | ||
| 16 | +../bsync-client/node_modules/.bin/cordova plugin add cordova-plugin-test-framework | ||
| 17 | + | ||
| 18 | +sed -i 's/index\.html/cdvtests\/index\.html/g' ./config.xml | ||
| 19 | + | ||
| 20 | +if [ $COMMAND == "run" ]; then | ||
| 21 | + ../bsync-client/node_modules/.bin/cordova run $PLATFORM | ||
| 22 | +else | ||
| 23 | + ../bsync-client/node_modules/.bin/cordova emulate $PLATFORM | ||
| 24 | +fi |
spec/browser-test.ts
0 → 100644
| 1 | +import PouchDB from 'pouchdb'; | ||
| 2 | +import {FileReplicator} from '../src/file-replicator'; | ||
| 3 | +import {ServiceLocator, ENV_UNKNOWN} from '../src/service-locator'; | ||
| 4 | +import {TestFileHandler} from './file-handler/test-file-handler'; | ||
| 5 | + | ||
| 6 | +ServiceLocator.addFileHandler(ENV_UNKNOWN, new TestFileHandler()); | ||
| 7 | + | ||
| 8 | +import '../src/browser-main'; | ||
| 9 | + | ||
| 10 | +declare var emit:any; | ||
| 11 | + | ||
| 12 | +const dbUrl = 'http://admin:admin@localhost:5984/pouch_test_db'; | ||
| 13 | + | ||
| 14 | +const testDocs = [ | ||
| 15 | + { 'type' : 'asset', 'source' : 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/292px-FullMoon2010.jpg' } , | ||
| 16 | + { 'type' : 'asset', 'source' : 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2b/Jupiter_and_its_shrunken_Great_Red_Spot.jpg/260px-Jupiter_and_its_shrunken_Great_Red_Spot.jpg' } , | ||
| 17 | + { 'type' : 'asset', 'source' : 'https://upload.wikimedia.org/wikipedia/commons/c/c7/Saturn_during_Equinox.jpg' } | ||
| 18 | +]; | ||
| 19 | + | ||
| 20 | +describe("Integration tests with couchdb", () => { | ||
| 21 | + | ||
| 22 | + let index = 1; | ||
| 23 | + let remoteDb = new PouchDB(dbUrl); | ||
| 24 | + let localDb; | ||
| 25 | + | ||
| 26 | + beforeAll((done) => { | ||
| 27 | + ServiceLocator.getFileReplicator().retryTimeout = 100; | ||
| 28 | + remoteDb.bulkDocs(testDocs).then(() => { | ||
| 29 | + done(); | ||
| 30 | + }); | ||
| 31 | + }); | ||
| 32 | + | ||
| 33 | + beforeEach(() => { | ||
| 34 | + localDb = new PouchDB('testdb-' + index); | ||
| 35 | + index++; | ||
| 36 | + | ||
| 37 | + localDb.put({ | ||
| 38 | + _id : "_design/index_type", | ||
| 39 | + views : { | ||
| 40 | + type : { | ||
| 41 | + map : function(doc) { | ||
| 42 | + if (doc.type) { emit(doc.type); } | ||
| 43 | + }.toString() | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | + }); | ||
| 47 | + | ||
| 48 | + }); | ||
| 49 | + | ||
| 50 | + it("Should successfully download several files", (done) => { | ||
| 51 | + TestFileHandler.setErrorRate(0); | ||
| 52 | + ServiceLocator.getFileReplicator().init(); | ||
| 53 | + let index = 0; | ||
| 54 | + | ||
| 55 | + localDb.replicate.from(dbUrl) | ||
| 56 | + .on('file-replicator-complete', event => { | ||
| 57 | + index++; | ||
| 58 | + }) | ||
| 59 | + .on('complete', () => { | ||
| 60 | + expect(index).toEqual(3); | ||
| 61 | + done(); | ||
| 62 | + }); | ||
| 63 | + }); | ||
| 64 | + | ||
| 65 | + it("Should trigger errors, but successfully download with retries", (done) => { | ||
| 66 | + TestFileHandler.setErrorRate(0.8); | ||
| 67 | + ServiceLocator.getFileReplicator().init(); | ||
| 68 | + let errors = 0; | ||
| 69 | + | ||
| 70 | + localDb.replicate.from(dbUrl) | ||
| 71 | + .on('file-replicator-error', event => { | ||
| 72 | + errors++; | ||
| 73 | + }) | ||
| 74 | + .on('complete', () => { | ||
| 75 | + expect(errors).toBeGreaterThanOrEqual(1); | ||
| 76 | + done(); | ||
| 77 | + }); | ||
| 78 | + }); | ||
| 79 | + | ||
| 80 | +}); |
spec/cordova/cordova-test.spec.js
0 → 100644
| 1 | +exports.defineAutoTests = function() { | ||
| 2 | + | ||
| 3 | + console.log(bsync); | ||
| 4 | + | ||
| 5 | + describe("Cordova tests", () => { | ||
| 6 | + /* | ||
| 7 | + let downloader = new CordovaDownloader(); | ||
| 8 | + | ||
| 9 | + it("should download sample image from https source and store with new name", (done) => { | ||
| 10 | + | ||
| 11 | + let source = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/800px-FullMoon2010.jpg"; | ||
| 12 | + let target = "cdvfile://full-moon.jpg"; | ||
| 13 | + let lastProgress = 0; | ||
| 14 | + | ||
| 15 | + downloader.download(source, target) | ||
| 16 | + .subscribe( | ||
| 17 | + (progress: number) => { | ||
| 18 | + expect(progress).toBeGreaterThan(lastProgress); | ||
| 19 | + lastProgress = progress; | ||
| 20 | + } , | ||
| 21 | + (error:any) => {} , | ||
| 22 | + () => { | ||
| 23 | + expect(lastProgress).toEqual(1); | ||
| 24 | + // expect(fs.existsSync(target)).toBeTruthy(); | ||
| 25 | + done(); | ||
| 26 | + } | ||
| 27 | + ); | ||
| 28 | + | ||
| 29 | + }); | ||
| 30 | + */ | ||
| 31 | + | ||
| 32 | + }); | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +}; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
spec/cordova/plugin.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" | ||
| 3 | + xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 4 | + id="bsync-client-test" | ||
| 5 | + version="1.0.0" | ||
| 6 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
| 7 | + | ||
| 8 | + <name>bsync cordova test</name> | ||
| 9 | + <license>Apache 2.0 License</license> | ||
| 10 | + | ||
| 11 | + <js-module src="cordova-test.spec.js" name="tests"> | ||
| 12 | + </js-module> | ||
| 13 | + | ||
| 14 | +</plugin> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
spec/file-handler/test-file-handler.ts
0 → 100644
| 1 | +import { Observable, Subscriber } from 'rxjs'; | ||
| 2 | +import { FileHandler } from '../../src/api/file-handler'; | ||
| 3 | + | ||
| 4 | +export class TestFileHandler implements FileHandler { | ||
| 5 | + | ||
| 6 | + protected static errorRate:number = 0; | ||
| 7 | + | ||
| 8 | + static setErrorRate(rate:number) { | ||
| 9 | + TestFileHandler.errorRate = rate; | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + download(source:string, target:string) : Observable<number> { | ||
| 13 | + return Observable.create((subscriber:Subscriber<number>) => { | ||
| 14 | + let random = Math.random(); | ||
| 15 | + let error:boolean = random < TestFileHandler.errorRate; | ||
| 16 | + let counter = 1; | ||
| 17 | + | ||
| 18 | + if (error) { | ||
| 19 | + subscriber.error("random error triggered"); | ||
| 20 | + return; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + let interval = setInterval(() => { | ||
| 24 | + if (counter < 4) { | ||
| 25 | + subscriber.next(counter * 25); | ||
| 26 | + } else { | ||
| 27 | + subscriber.complete(); | ||
| 28 | + clearInterval(interval); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + ++counter; | ||
| 32 | + }, 10); | ||
| 33 | + }); | ||
| 34 | + } | ||
| 35 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
spec/node-test.ts
0 → 100644
spec/support/jasmine.json
0 → 100644
spec/test/file-handler/node-file-handler.ts
0 → 100644
| 1 | +import { NodeFileHandler } from '../../../src/file-handler/node-file-handler'; | ||
| 2 | +import * as http from 'http'; | ||
| 3 | +import * as https from 'https'; | ||
| 4 | +import * as fs from 'fs'; | ||
| 5 | + | ||
| 6 | +describe("Node Downloader", () => { | ||
| 7 | + | ||
| 8 | + let nodeFileHandler = new NodeFileHandler(); | ||
| 9 | + | ||
| 10 | + it("should retrieve right handler", () => { | ||
| 11 | + | ||
| 12 | + let httpHandler = nodeFileHandler.selectProtocol("http://someurl.com/image.jpg"); | ||
| 13 | + expect(httpHandler).toEqual(http); | ||
| 14 | + | ||
| 15 | + let httpsHandler = nodeFileHandler.selectProtocol("https://someurl.com/image.jpg"); | ||
| 16 | + expect(httpsHandler).toEqual(https); | ||
| 17 | + | ||
| 18 | + let nullHandler = nodeFileHandler.selectProtocol("notsupported://blub"); | ||
| 19 | + expect(nullHandler).toBeNull(); | ||
| 20 | + | ||
| 21 | + }); | ||
| 22 | + | ||
| 23 | + it("should download sample image from https source and store with new name", (done) => { | ||
| 24 | + | ||
| 25 | + let source = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/800px-FullMoon2010.jpg"; | ||
| 26 | + let target = ".tmp/full-moon.jpg"; | ||
| 27 | + let lastProgress = 0; | ||
| 28 | + | ||
| 29 | + nodeFileHandler.download(source, target) | ||
| 30 | + .subscribe( | ||
| 31 | + (progress: number) => { | ||
| 32 | + expect(progress).toBeGreaterThan(lastProgress); | ||
| 33 | + lastProgress = progress; | ||
| 34 | + } , | ||
| 35 | + (error:any) => {} , | ||
| 36 | + () => { | ||
| 37 | + expect(lastProgress).toEqual(1); | ||
| 38 | + expect(fs.existsSync(target)).toBeTruthy(); | ||
| 39 | + done(); | ||
| 40 | + } | ||
| 41 | + ); | ||
| 42 | + }); | ||
| 43 | + | ||
| 44 | + 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"; | ||
| 46 | + let target = ".tmp/full-moon-sensless.jpg"; | ||
| 47 | + let lastProgress = 0; | ||
| 48 | + | ||
| 49 | + let file = fs.createWriteStream(target, {'flags': 'a'}); | ||
| 50 | + let dummyData = "some sensless information to fill bytes..."; | ||
| 51 | + | ||
| 52 | + file.write(new Buffer(dummyData)); | ||
| 53 | + file.end(() => { | ||
| 54 | + | ||
| 55 | + nodeFileHandler.download(source, target) | ||
| 56 | + .subscribe( | ||
| 57 | + () => { fail("progress should not be called"); } , | ||
| 58 | + () => {} , | ||
| 59 | + () => { | ||
| 60 | + expect(fs.existsSync(target)).toBeTruthy(); | ||
| 61 | + done(); | ||
| 62 | + } | ||
| 63 | + ); | ||
| 64 | + | ||
| 65 | + }); | ||
| 66 | + }); | ||
| 67 | + | ||
| 68 | +}); | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
spec/test/file-replicator.ts
0 → 100644
| 1 | +import { FileReplicator } from '../../src/file-replicator'; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +describe("File Replicator", () => { | ||
| 5 | + | ||
| 6 | + let fileReplicator = new FileReplicator(); | ||
| 7 | + | ||
| 8 | + let change = { | ||
| 9 | + docs : [ | ||
| 10 | + { language: 'de', type : "asset", source : "http://someplace.com/icon.jpg" , target : "icon.jpg" } , | ||
| 11 | + { language: 'de', type : "asset", source : "http://sampleuri.com/image.png" } , | ||
| 12 | + { language: 'en', type : "asset", source : "https://secureasset.com/asset.mp3" , target : "music.mp3" } | ||
| 13 | + ] | ||
| 14 | + }; | ||
| 15 | + | ||
| 16 | + beforeEach(() => { | ||
| 17 | + fileReplicator.init(); | ||
| 18 | + }); | ||
| 19 | + | ||
| 20 | + it("should contain several assets", () => { | ||
| 21 | + fileReplicator.pushChanges(change); | ||
| 22 | + expect(fileReplicator.files.length).toEqual(3); | ||
| 23 | + }); | ||
| 24 | + | ||
| 25 | + it("should get correct asset names", () => { | ||
| 26 | + let files = fileReplicator.prepareFiles(change.docs); | ||
| 27 | + | ||
| 28 | + expect(files[0].target).toEqual("icon.jpg"); | ||
| 29 | + expect(files[1].target).toEqual("bsync_707608502"); | ||
| 30 | + expect(files[2].target).toEqual("music.mp3"); | ||
| 31 | + }); | ||
| 32 | + | ||
| 33 | + it("should only get items with language=de", () => { | ||
| 34 | + fileReplicator.itemValidator = (item:any) => { | ||
| 35 | + if (item && item.language === "de") { | ||
| 36 | + return true; | ||
| 37 | + } | ||
| 38 | + return false; | ||
| 39 | + }; | ||
| 40 | + | ||
| 41 | + let files = fileReplicator.prepareFiles(change.docs); | ||
| 42 | + | ||
| 43 | + expect(files.length).toEqual(2); | ||
| 44 | + expect(files[0].target).toEqual("icon.jpg"); | ||
| 45 | + expect(files[1].target).toEqual("bsync_707608502"); | ||
| 46 | + | ||
| 47 | + fileReplicator.itemValidator = null; | ||
| 48 | + }); | ||
| 49 | + | ||
| 50 | +}); | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/api/file-handler.ts
0 → 100644
| 1 | +import { Observable } from 'rxjs/Observable'; | ||
| 2 | + | ||
| 3 | +export interface FileHandler { | ||
| 4 | + | ||
| 5 | + /** | ||
| 6 | + * The donwload works as following: | ||
| 7 | + * - if the file already exists and is not empty: trigger complete | ||
| 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 | ||
| 10 | + */ | ||
| 11 | + download(source:string, target:string) : Observable<number>; | ||
| 12 | + | ||
| 13 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/api/file.ts
0 → 100644
src/browser-main.ts
0 → 100644
| 1 | +import { EventEmitter } from 'events'; | ||
| 2 | +import { ServiceLocator } from './service-locator'; | ||
| 3 | + | ||
| 4 | +export * from './service-locator'; | ||
| 5 | + | ||
| 6 | +export function loadBsyncPlugin (PouchDB) { | ||
| 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 | +} |
src/config.ts
0 → 100644
| 1 | +export const CONFIG_ITEM_KEY = "itemKey"; | ||
| 2 | +export const CONFIG_ITEM_VALUE = "itemValue"; | ||
| 3 | +export const CONFIG_ITEM_SOURCE_ATTRIBUTE = "itemSourceAttribute"; | ||
| 4 | +export const CONFIG_ITEM_TARGET_ATTRIBUTE = "itemTargetAttribute"; | ||
| 5 | +export const CONFIG_ITEM_VALIDATOR = "itemValidator"; | ||
| 6 | +export const CONFIG_RETRY_TIMEOUT = "retryTimeout"; | ||
| 7 | +export const CONFIG_FILE_HANDLER = "fileHandler"; | ||
| 8 | + | ||
| 9 | +export class Config { | ||
| 10 | + | ||
| 11 | + protected config:any = {}; | ||
| 12 | + | ||
| 13 | + hasConfig(key:string) { | ||
| 14 | + if (this.config[key]) { | ||
| 15 | + return true; | ||
| 16 | + } | ||
| 17 | + return false; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + getConfig(key:string) { | ||
| 21 | + return this.config[key]; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + setConfig(key:string, value:any) { | ||
| 25 | + this.config[key] = value; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/file-handler/cordova-file-handler.ts
0 → 100644
| 1 | +import { FileHandler } from '../api/file-handler'; | ||
| 2 | + | ||
| 3 | +declare var Rx; | ||
| 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 | + | ||
| 14 | + let fileTransfer = new window['FileTransfer'](); | ||
| 15 | + | ||
| 16 | + fileTransfer.onprogress = (progress:ProgressEvent) => { | ||
| 17 | + subscriber.next(progress.total / progress.loaded); | ||
| 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 | + } | ||
| 34 | + | ||
| 35 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/file-handler/electron-file-handler.ts
0 → 100644
| 1 | +import { Observable, Subscriber } from 'rxjs'; | ||
| 2 | +import { FileHandler } from '../api/file-handler'; | ||
| 3 | + | ||
| 4 | +export class ElectronFileHandler implements FileHandler { | ||
| 5 | + | ||
| 6 | + constructor (private ipcRenderer:any) { | ||
| 7 | + } | ||
| 8 | + | ||
| 9 | + download(source:string, target:string) : Observable<number> { | ||
| 10 | + return Observable.create((subscriber:Subscriber<number>) => { | ||
| 11 | + | ||
| 12 | + this.ipcRenderer.once('bsync-download-complete', () => { | ||
| 13 | + this.ipcRenderer.removeAllListeners('bsync-download-progress'); | ||
| 14 | + this.ipcRenderer.removeAllListeners('bsync-download-error'); | ||
| 15 | + subscriber.complete(); | ||
| 16 | + }); | ||
| 17 | + | ||
| 18 | + this.ipcRenderer.on('bsync-download-progress', (progress:number) => { | ||
| 19 | + subscriber.next(progress); | ||
| 20 | + }); | ||
| 21 | + | ||
| 22 | + this.ipcRenderer.once('bsync-download-error', (error:any) => { | ||
| 23 | + this.ipcRenderer.removeAllListeners('bsync-download-progress'); | ||
| 24 | + this.ipcRenderer.removeAllListeners('bsync-download-complete'); | ||
| 25 | + subscriber.error(error); | ||
| 26 | + }); | ||
| 27 | + | ||
| 28 | + this.ipcRenderer.send('bsync-download', { | ||
| 29 | + source : source , | ||
| 30 | + target : target | ||
| 31 | + }); | ||
| 32 | + }); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/file-handler/node-file-handler.ts
0 → 100644
| 1 | +import { Observable, Subscriber } from 'rxjs'; | ||
| 2 | +import { FileHandler } from '../api/file-handler'; | ||
| 3 | +import * as http from 'http'; | ||
| 4 | +import * as https from 'https'; | ||
| 5 | +import * as fs from 'fs'; | ||
| 6 | + | ||
| 7 | +export class NodeFileHandler implements FileHandler { | ||
| 8 | + | ||
| 9 | + selectProtocol(url:string) : any { | ||
| 10 | + if (url.search(/^http:\/\//) === 0) { | ||
| 11 | + return http; | ||
| 12 | + } else if (url.search(/^https:\/\//) === 0) { | ||
| 13 | + return https; | ||
| 14 | + } else { | ||
| 15 | + return null; | ||
| 16 | + } | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + download(source:string, target:string) : Observable<number> { | ||
| 20 | + | ||
| 21 | + let handler = this.selectProtocol(source); | ||
| 22 | + | ||
| 23 | + return Observable.create((subscriber:Subscriber<number>) => { | ||
| 24 | + | ||
| 25 | + if (!handler) { | ||
| 26 | + subscriber.error("No handler for source: " + source); | ||
| 27 | + return; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + // file already exists and is not empty | ||
| 31 | + if (fs.existsSync(target) && (fs.statSync(target)['size'] > 0)) { | ||
| 32 | + subscriber.complete(); | ||
| 33 | + return; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + let file = fs.createWriteStream(target, {'flags': 'a'}); | ||
| 37 | + | ||
| 38 | + handler.get(source, (response) => { | ||
| 39 | + let size = response.headers['content-length']; // in bytes | ||
| 40 | + let prog = 0; // already downloaded | ||
| 41 | + let progCounts = 100; // how many progress events should be triggerd (1-100 %) | ||
| 42 | + let nextProg = (1/progCounts); | ||
| 43 | + | ||
| 44 | + response.on('data', (chunk) => { | ||
| 45 | + prog += chunk.length; | ||
| 46 | + file.write(chunk, 'binary'); | ||
| 47 | + | ||
| 48 | + if ((prog / size) > nextProg) { | ||
| 49 | + subscriber.next(prog / size); | ||
| 50 | + nextProg += (1 / progCounts); | ||
| 51 | + } | ||
| 52 | + }); | ||
| 53 | + | ||
| 54 | + response.on('end', () => { | ||
| 55 | + file.end(); | ||
| 56 | + subscriber.complete(); | ||
| 57 | + }); | ||
| 58 | + | ||
| 59 | + }).on('error', (error) => { | ||
| 60 | + fs.unlink(target); | ||
| 61 | + subscriber.error("Error while downloading: " + error); | ||
| 62 | + }); | ||
| 63 | + | ||
| 64 | + }); | ||
| 65 | + | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/file-replicator.ts
0 → 100644
| 1 | +import {FileHandler} from './api/file-handler'; | ||
| 2 | +import {File} from './api/file'; | ||
| 3 | +import {Util} from './util'; | ||
| 4 | +import {EventEmitter} from 'events'; | ||
| 5 | + | ||
| 6 | +export class FileReplicator extends EventEmitter { | ||
| 7 | + | ||
| 8 | + constructor() { | ||
| 9 | + super(); | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + protected _files:Array<File> = []; | ||
| 13 | + | ||
| 14 | + protected _itemValidator: (item:any) => boolean = null; | ||
| 15 | + protected _fileHandler:FileHandler = null; | ||
| 16 | + protected _retryTimeout:number = 0; | ||
| 17 | + | ||
| 18 | + protected _itemKey = "type"; | ||
| 19 | + protected _itemValue = "asset"; | ||
| 20 | + protected _itemSourceAttribute = "source"; | ||
| 21 | + protected _itemTargetAttribute = "target"; | ||
| 22 | + | ||
| 23 | + get files(): Array<File> { | ||
| 24 | + return this._files; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + set fileHandler (handler:FileHandler) { | ||
| 28 | + this._fileHandler = handler; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + set retryTimeout (timeout:number) { | ||
| 32 | + this._retryTimeout = timeout; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + set itemValidator(validator:(item:any) => boolean) { | ||
| 36 | + this._itemValidator = validator; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + set itemKey(key:string) { | ||
| 40 | + this._itemKey = key; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + set itemValue(value:string) { | ||
| 44 | + this._itemValue = value; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + set itemSourceAttribute(sourceAttribute:string) { | ||
| 48 | + this._itemSourceAttribute = sourceAttribute; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + set itemTargetAttribute(targetAttribute:string) { | ||
| 52 | + this._itemTargetAttribute = targetAttribute; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + get itemKey() { | ||
| 56 | + return this._itemKey; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + get itemValue() { | ||
| 60 | + return this._itemValue; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + get itemSourceAttribute() { | ||
| 64 | + return this._itemSourceAttribute; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + get itemTargetAttribute() { | ||
| 68 | + return this._itemTargetAttribute; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + init(files: Array<File> = []) { | ||
| 72 | + this._files = files; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * change from pouchdb replicate | ||
| 77 | + */ | ||
| 78 | + pushChanges(change:any) { | ||
| 79 | + let items:Array<any> = []; | ||
| 80 | + | ||
| 81 | + if (change && change.docs && change.docs.length > 0) { | ||
| 82 | + for (let item of change.docs) { | ||
| 83 | + if (item[this._itemKey] && item[this._itemKey] === this._itemValue) { | ||
| 84 | + items.push(item); | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + let files = this.prepareFiles(items); | ||
| 90 | + | ||
| 91 | + for (let file of files) { | ||
| 92 | + this._files.push(file); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + downloadFiles(files:Array<File>, fileHandler:FileHandler, index:number = 0) { | ||
| 97 | + if (index >= files.length) { | ||
| 98 | + return; | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + this.emit('start', { progress: 0, index : index, length : files.length }); | ||
| 102 | + | ||
| 103 | + fileHandler | ||
| 104 | + .download(files[index].source, files[index].target) | ||
| 105 | + .subscribe( | ||
| 106 | + progress => { | ||
| 107 | + this.emit('progress', { progress : progress, index : index, length : files.length }) | ||
| 108 | + } , | ||
| 109 | + error => { | ||
| 110 | + this.emit('error', { progress : 0, index : index, length : files.length, error: error }); | ||
| 111 | + } , | ||
| 112 | + () => { | ||
| 113 | + this.emit('complete', { progress : 100 , index : index, length : files.length }); | ||
| 114 | + this.downloadFiles(files, fileHandler, index+1); | ||
| 115 | + } | ||
| 116 | + ); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + prepareFiles(items: Array<any>) : Array<File> { | ||
| 120 | + let output = []; | ||
| 121 | + | ||
| 122 | + for (let item of items) { | ||
| 123 | + if (item[this._itemSourceAttribute] && (!this._itemValidator || this._itemValidator(item))) { | ||
| 124 | + let file = { source : item[this._itemSourceAttribute] , target : '' }; | ||
| 125 | + | ||
| 126 | + if (item[this._itemTargetAttribute]) { | ||
| 127 | + file.target = item[this._itemTargetAttribute]; | ||
| 128 | + } else { | ||
| 129 | + file.target = Util.getNameHash(file.source); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + output.push(file); | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + return output; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + start() { | ||
| 140 | + this.on('complete', (event:any) => { | ||
| 141 | + if ((event.index + 1) >= event.length) { | ||
| 142 | + this.replicationFinalized(event.index); | ||
| 143 | + } | ||
| 144 | + }); | ||
| 145 | + | ||
| 146 | + this.on('error', (event:any) => { | ||
| 147 | + this.replicationFinalized(event.index); | ||
| 148 | + }); | ||
| 149 | + | ||
| 150 | + this.downloadFiles(this._files, this._fileHandler); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + replicationFinalized(lastIndex:number) { | ||
| 154 | + if (lastIndex+1 >= this._files.length) { // all finished | ||
| 155 | + this._files = []; | ||
| 156 | + this.emit('final'); | ||
| 157 | + } else if (this._retryTimeout > 0) { // restart after last success | ||
| 158 | + this._files.splice(0,lastIndex); | ||
| 159 | + setTimeout(() => { | ||
| 160 | + this.downloadFiles(this._files, this._fileHandler); | ||
| 161 | + }, this._retryTimeout); | ||
| 162 | + } | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/node-main.ts
0 → 100644
| 1 | +import { NodeFileHandler } from './file-handler/node-file-handler'; | ||
| 2 | + | ||
| 3 | +export default class Bsync { | ||
| 4 | + | ||
| 5 | + static configIpcMain(ipcMain: any, downloadDir:string) { | ||
| 6 | + let nodeFileHander = new NodeFileHandler(); | ||
| 7 | + | ||
| 8 | + ipcMain.on('bsync-download', (event, args) => { | ||
| 9 | + nodeFileHander.download(args.source, downloadDir + args.target) | ||
| 10 | + .subscribe( | ||
| 11 | + (progress:number) => { event.sender.send('bsync-download-progress', progress); } , | ||
| 12 | + (error:any) => { event.sender.send('bsync-download-error', error); } , | ||
| 13 | + () => { event.sender.send('bsync-download-complete'); } | ||
| 14 | + ); | ||
| 15 | + }); | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/service-locator.ts
0 → 100644
| 1 | +import {FileHandler} from './api/file-handler'; | ||
| 2 | +import {ElectronFileHandler} from './file-handler/electron-file-handler'; | ||
| 3 | +import {FileReplicator} from './file-replicator'; | ||
| 4 | +import { | ||
| 5 | + Config, | ||
| 6 | + CONFIG_RETRY_TIMEOUT, | ||
| 7 | + CONFIG_ITEM_KEY, | ||
| 8 | + CONFIG_ITEM_VALUE, | ||
| 9 | + CONFIG_ITEM_TARGET_ATTRIBUTE, | ||
| 10 | + CONFIG_ITEM_SOURCE_ATTRIBUTE | ||
| 11 | +} from './config'; | ||
| 12 | + | ||
| 13 | +export const ENV_ELECTRON = "electron"; | ||
| 14 | +export const ENV_CORDOVA = "cordova"; | ||
| 15 | +export const ENV_UNKNOWN = "unknown"; | ||
| 16 | + | ||
| 17 | +export class ServiceLocator { | ||
| 18 | + | ||
| 19 | + protected static fileHandlers:any = {}; | ||
| 20 | + protected static fileReplicator: FileReplicator; | ||
| 21 | + protected static config: Config; | ||
| 22 | + | ||
| 23 | + static addFileHandler(environment:string, fileHandler:FileHandler) { | ||
| 24 | + ServiceLocator.fileHandlers[environment] = fileHandler; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + static getConfig() : Config { | ||
| 28 | + if (!ServiceLocator.config) { | ||
| 29 | + ServiceLocator.config = new Config(); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + return ServiceLocator.config; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + static getEnvironment() { | ||
| 36 | + if (typeof window['require'] === 'function' && window['require']('electron')) { | ||
| 37 | + return ENV_ELECTRON; | ||
| 38 | + } | ||
| 39 | + if (typeof window['FileTransfer'] === 'function') { | ||
| 40 | + return ENV_CORDOVA; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + return ENV_UNKNOWN; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + static getFileHandler() : FileHandler { | ||
| 47 | + let environment = ServiceLocator.getEnvironment(); | ||
| 48 | + | ||
| 49 | + if (ServiceLocator.fileHandlers[environment]) { | ||
| 50 | + return ServiceLocator.fileHandlers[environment]; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + if (environment === ENV_ELECTRON) { | ||
| 54 | + return new ElectronFileHandler(window['require']('electron').ipcRenderer); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + return null; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + static getFileReplicator() : FileReplicator { | ||
| 61 | + if (!ServiceLocator.fileReplicator) { | ||
| 62 | + | ||
| 63 | + ServiceLocator.fileReplicator = new FileReplicator(); | ||
| 64 | + | ||
| 65 | + ServiceLocator.fileReplicator.fileHandler = ServiceLocator.getFileHandler(); | ||
| 66 | + | ||
| 67 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_RETRY_TIMEOUT)) { | ||
| 68 | + ServiceLocator.fileReplicator.retryTimeout = ServiceLocator.getConfig().getConfig(CONFIG_RETRY_TIMEOUT); | ||
| 69 | + } | ||
| 70 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_KEY)) { | ||
| 71 | + ServiceLocator.fileReplicator.itemKey = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_KEY); | ||
| 72 | + } | ||
| 73 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_VALUE)) { | ||
| 74 | + ServiceLocator.fileReplicator.itemValue = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_VALUE); | ||
| 75 | + } | ||
| 76 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE)) { | ||
| 77 | + ServiceLocator.fileReplicator.itemSourceAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_SOURCE_ATTRIBUTE); | ||
| 78 | + } | ||
| 79 | + if (ServiceLocator.getConfig().hasConfig(CONFIG_ITEM_TARGET_ATTRIBUTE)) { | ||
| 80 | + ServiceLocator.fileReplicator.itemTargetAttribute = ServiceLocator.getConfig().getConfig(CONFIG_ITEM_TARGET_ATTRIBUTE); | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + return ServiceLocator.fileReplicator; | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment