diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..eebc6dfc --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "shorthand-resolver": "https://github.com/{{owner}}/{{package}}.git" +} \ No newline at end of file diff --git a/.eslintrc b/.eslintrc_reserved similarity index 100% rename from .eslintrc rename to .eslintrc_reserved diff --git a/.jshintrc b/.jshintrc index d3637e3f..d5e54aeb 100644 --- a/.jshintrc +++ b/.jshintrc @@ -28,7 +28,7 @@ "eqeqeq": true, "forin": true, "immed": true, - "latedef": true, + "latedef": "nofunc", "newcap": true, "noarg": true, "noempty": true, diff --git a/apps/ide/src/dojoConfig.js b/apps/ide/src/dojoConfig.js index 3a5b0de2..407fa7fa 100644 --- a/apps/ide/src/dojoConfig.js +++ b/apps/ide/src/dojoConfig.js @@ -14,23 +14,38 @@ * limitations under the License. */ -(function (global) { +(function (globalObject) { 'use strict'; + + // in electron, we should remove global.require & global.module + // to make dojo & other amd module work + // how can we detect that we're working in electron? + if (typeof(globalObject.process) === 'object' && typeof(globalObject.require) === 'function' && + typeof(globalObject.module) === 'object') { + globalObject.nrequire = globalObject.require; + globalObject.nmodule = globalObject.module; + delete globalObject.require; + //noinspection JSAnnotator + delete globalObject.module; + globalObject.__ELECTRON_BROWSER__ = true; + } + var webidaLocale = decodeURIComponent( document.cookie.replace(/(?:(?:^|.*;\s*)webida\.locale\s*\=\s*([^;]*).*$)|^.*$/, '$1') ); - global.dojoConfig = { + globalObject.dojoConfig = { async: true, baseUrl: '../../../bower_components', parseOnLoad: false, packages: [ - {name: 'xstyle', location: './xstyle'}, - {name: 'put-selector', location: './put-selector'}, + {name: 'dijit', location: './dijit'}, + {name: 'dojo', location: './dojo'}, + {name: 'dojox', location: './dojox'}, {name: 'jquery', location: './jquery/dist', main: 'jquery.min'}, + {name: 'put-selector', location: './put-selector'}, {name: 'showdown', location: './showdown/dist', main: 'showdown.min'}, - {name: 'dojo', location: './dojo'}, - {name: 'dijit', location: './dijit'}, - {name: 'dojox', location: './dojox'} + {name: 'URIjs', location:'./URIjs/src', main:'URI.min'}, + {name: 'xstyle', location: './xstyle'} ], locale: ((webidaLocale === 'default') || (webidaLocale === '')) ? (location.search.match(/locale=([\w\-]+)/) ? RegExp.$1 : 'en-us') : webidaLocale, @@ -44,14 +59,45 @@ }, aliases: [ ['text', 'dojo/text'], + ['external/lodash/lodash.min', 'external/lodash/dist/lodash.min'], // new 4.x lodash changed bundle path. ['popup-dialog', 'webida-lib/widgets/dialogs/popup-dialog/PopupDialog'], // TODO should use these below aliases for versioned resources - ['webida', 'webida-lib/webida-0.3'], ['FSCache', 'webida-lib/FSCache-0.1'], ['plugin-manager', 'webida-lib/plugin-manager-0.1'], - ['msg', 'webida-lib/msg.js'], // diff_match_patch is used in codemirror - ['diff_match_patch', '//cdnjs.cloudflare.com/ajax/libs/diff_match_patch/20121119/diff_match_patch.js'] + ['diff_match_patch', '//cdnjs.cloudflare.com/ajax/libs/diff_match_patch/20121119/diff_match_patch.js'], + + // following server api will be removed when window.nrequire is true + // so, put these always at the end of aliases array + ['webida-lib/server-api', 'webida-lib/webida-0.3'], + ['webida-lib/server-pubsub', 'webida-lib/msg'] ] }; + + // twick requirejs alias to use new server-api when not using using legacy server + // &, for new server api, additional package 'webida-restful-api' is required. + if (window.location.href.indexOf('legacy=') < 0 ) { + var dojoConfig = globalObject.dojoConfig; + dojoConfig.packages.push( { + name: 'webida-service-client', + location: './webida-service-client', + main: 'webida-service-client-bundle' + }); + dojoConfig.aliases.pop(); + dojoConfig.aliases.pop(); + dojoConfig.aliases.push(['webida-lib/server-api', 'webida-service-client']); + dojoConfig.aliases.push(['webida-lib/server-pubsub', 'webida-lib/server-pubsub-compat']); + dojoConfig.aliases.push(['top/site-config.json', 'top/site-config-desktop.json']); + dojoConfig.aliases.push(['urijs', 'URIjs']); + } + + if (globalObject.__ELECTRON_BROWSER__) { + // Although dojo may understand cjs require() by building option, + // some bower modules works under amd system only. + // So, we don't allow to mix amd/cjs modules with same require function + globalObject.dojoConfig.has = { + 'host-node': false // Prevent dojo from being fooled by Electron + }; + } + })(window); diff --git a/apps/ide/src/index.html b/apps/ide/src/index.html index fc03ee26..5e454e94 100644 --- a/apps/ide/src/index.html +++ b/apps/ide/src/index.html @@ -19,12 +19,14 @@ 'webida-lib/app', 'webida-lib/app-config', 'webida-lib/theme', - 'webida-lib/util/load-polyfills' + 'webida-lib/util/load-polyfills', + 'external/URIjs/src/URI' ], function ( ide, config, theme, - loader + loader, + URI ) { 'use strict'; theme.loadTheme(); @@ -37,6 +39,47 @@ ga('create',config.googleAnalytics, 'auto'); ga('send', 'pageview'); } + + var uri = new URI(window.location.href); + var bootArgs = uri.query(true); + + if (bootArgs.legacy) { + window.legacySiteServerConfig = desktopConfig.legacySiteConfigs[bootArgs.legacy]; + if (!window.legacySiteServerConfig) { + alert('cannot read site configuration for legacy remote server'); + } + } + + if (window.__ELECTRON_BROWSER__) { + var browserWindow = window.nrequire('electron').remote.getCurrentWindow(); + var storageKey = 'webida-desktop-window-status-' + bootArgs.workspace; + var winStatus = localStorage.getItem(storageKey); + if (!winStatus) { + console.log('unable to load window status - fallback to config defaults '); + var desktopConfig = localStorage.getItem('webida-desktop-config'); + if (desktopConfig) { + desktopConfig = JSON.parse(desktopConfig); + } + winStatus = desktopConfig.ideWindowDefaults; + } else { + winStatus = JSON.parse(winStatus); + } + + if (winStatus.x) { + browserWindow.setBounds(winStatus); + } else { + browserWindow.setSize(winStatus.width, winStatus.height); + } + if (winStatus.isMaximized) { + browserWindow.maximze(); + } + + browserWindow.on('close', function(event) { + var newStatus = browserWindow.getBounds(); + newStatus.isMaximized = browserWindow.isMaximized(); + localStorage.setItem(storageKey, JSON.stringify(newStatus)); + }); + } }); diff --git a/apps/ide/src/plugins/plugin-settings-desktop.json b/apps/ide/src/plugins/plugin-settings-desktop.json new file mode 100644 index 00000000..82551e41 --- /dev/null +++ b/apps/ide/src/plugins/plugin-settings-desktop.json @@ -0,0 +1,43 @@ +{ + "plugins" : [ + "plugins/webida.editor.text-editor", + "plugins/webida.editor.code-editor", + "plugins/webida.editor.code-editor.ca.css.lint", + "plugins/webida.editor.code-editor.ca.html.lint", + "plugins/webida.editor.code-editor.ca.js.lint", + "plugins/webida.editor.code-editor.ca.json.lint", + "plugins/webida.editor.code-editor.content-assist.beautify", + "plugins/webida.editor.code-editor.content-assist.comments", + "plugins/webida.editor.code-editor.content-assist.css.css-smart", + "plugins/webida.editor.code-editor.content-assist.css.default", + "plugins/webida.editor.code-editor.content-assist.html.htmlsmart", + "plugins/webida.editor.code-editor.content-assist.html.html-link", + "plugins/webida.editor.code-editor.content-assist.html.default", + "plugins/webida.editor.code-editor.content-assist.tern", + "plugins/project-configurator", + "plugins/webida.ide.project-management.run", + "plugins/webida.ide.project.deploy", + "plugins/help", + "plugins/webida.preference", + "plugins/webida.plugin-setting", + "plugins/webida.ide.search-result", + "plugins/webida.locale", + "plugins/webida.ide.notification.view", + "plugins/webida.ide.notification.toast", + "webida-lib/plugins/command-system", + "webida-lib/plugins/editors", + "webida-lib/plugins/fs-commands", + "webida-lib/plugins/git", + "webida-lib/plugins/output", + "webida-lib/plugins/preview", + "webida-lib/plugins/session-event-dispatcher", + "webida-lib/plugins/workspace", + "webida-lib/plugins/workbench" + ], + "start-plugins" : [ + "webida-lib/plugins/command-system", + "webida-lib/plugins/session-event-dispatcher", + "webida-lib/plugins/workbench", + "plugins/webida.ide.notification.toast" + ] +} diff --git a/apps/ide/src/plugins/project-wizard/build/build.js b/apps/ide/src/plugins/project-wizard/build/build.js index 564af90d..f9e9fb9e 100644 --- a/apps/ide/src/plugins/project-wizard/build/build.js +++ b/apps/ide/src/plugins/project-wizard/build/build.js @@ -29,7 +29,7 @@ define([ 'webida-lib/app', 'webida-lib/util/logger/logger-client', 'webida-lib/util/path', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/dialogs/buttoned-dialog/ButtonedDialog', './buildProfile', '../constants', diff --git a/apps/ide/src/plugins/project-wizard/build/signing-new.js b/apps/ide/src/plugins/project-wizard/build/signing-new.js index 6ae3ec6b..e1449686 100644 --- a/apps/ide/src/plugins/project-wizard/build/signing-new.js +++ b/apps/ide/src/plugins/project-wizard/build/signing-new.js @@ -1,12 +1,12 @@ /* * Copyright (c) 2012-2015 S-Core Co., Ltd. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,7 +27,7 @@ define([ 'dijit/registry', 'dojo/Deferred', 'webida-lib/util/path', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/dialogs/buttoned-dialog/ButtonedDialog', 'text!plugins/project-wizard/layer/export-signing-new.html', './buildProfile', @@ -92,6 +92,43 @@ define([ NewSigning.prototype.openDialog = function () { var self = this; var data = null; + var deferred = new Deferred(); + + var dlg = new ButtonedDialog({ + buttons: [ + { id: 'signingCreate', + caption: 'OK', + methodOnClick: 'checkAndCreate' + }, + { id: 'signingCancel', + caption: 'Cancel', + methodOnClick: 'hide' + } + ], + methodOnEnter: 'checkAndCreate', + title: Messages.GEN_SIGNED_WIZARD_NEW_KEY_STORE, + refocus: false, + + onHide: function () { + dlg.destroyRecursive(); + return deferred.resolve(data); + }, + + onLoad: function () { + }, + + checkAndCreate: function () { + _super.setMessage(''); + var newName = reg.byId('alias').get('value'); + try { + if (self.validate()) { + doCreation(newName); + } + } catch (err) { + _super.setError(err); + } + } + }); function doCreation(newName) { var alias = newName; @@ -125,42 +162,6 @@ define([ }); } - var deferred = new Deferred(); - var dlg = new ButtonedDialog({ - buttons: [ - { id: 'signingCreate', - caption: 'OK', - methodOnClick: 'checkAndCreate' - }, - { id: 'signingCancel', - caption: 'Cancel', - methodOnClick: 'hide' - } - ], - methodOnEnter: 'checkAndCreate', - title: Messages.GEN_SIGNED_WIZARD_NEW_KEY_STORE, - refocus: false, - - onHide: function () { - dlg.destroyRecursive(); - return deferred.resolve(data); - }, - - onLoad: function () { - }, - - checkAndCreate: function () { - _super.setMessage(''); - var newName = reg.byId('alias').get('value'); - try { - if (self.validate()) { - doCreation(newName); - } - } catch (err) { - _super.setError(err); - } - } - }); _super.setId(dlg.id); dlg.set('doLayout', false); dlg.setContentArea(tplLayout); diff --git a/apps/ide/src/plugins/project-wizard/build/signing-select.js b/apps/ide/src/plugins/project-wizard/build/signing-select.js index 6a01ebc7..77bea43c 100644 --- a/apps/ide/src/plugins/project-wizard/build/signing-select.js +++ b/apps/ide/src/plugins/project-wizard/build/signing-select.js @@ -28,7 +28,7 @@ define([ 'dojo', 'dojo/Deferred', 'dojo/store/Memory', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/dialogs/buttoned-dialog/ButtonedDialog', 'text!plugins/project-wizard/layer/export-signing-select.html', './buildProfile', diff --git a/apps/ide/src/plugins/project-wizard/commands.js b/apps/ide/src/plugins/project-wizard/commands.js index 892d7753..4dcf67dc 100644 --- a/apps/ide/src/plugins/project-wizard/commands.js +++ b/apps/ide/src/plugins/project-wizard/commands.js @@ -28,7 +28,7 @@ define([ 'dojo/topic', 'lib/test/bootstrap/bootstrap.custom', 'webida-lib/app', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/util/locale', 'webida-lib/widgets/dialogs/buttoned-dialog/ButtonedDialog', 'text!./layer/pw-layout.html', diff --git a/apps/ide/src/plugins/project-wizard/constants.js b/apps/ide/src/plugins/project-wizard/constants.js index bfbba420..2f61d56d 100644 --- a/apps/ide/src/plugins/project-wizard/constants.js +++ b/apps/ide/src/plugins/project-wizard/constants.js @@ -19,7 +19,7 @@ * @author kh5325.kim@samsung.com */ define([ - 'webida-lib/webida-0.3' + 'webida-lib/server-api' ], function ( webida ) { diff --git a/apps/ide/src/plugins/project-wizard/device/device-select.js b/apps/ide/src/plugins/project-wizard/device/device-select.js index 5c2443b2..71e5667c 100644 --- a/apps/ide/src/plugins/project-wizard/device/device-select.js +++ b/apps/ide/src/plugins/project-wizard/device/device-select.js @@ -29,7 +29,7 @@ define([ 'dojo/store/Memory', 'dojox/grid/EnhancedGrid', 'dojox/grid/enhanced/plugins/IndirectSelection', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/dialogs/buttoned-dialog/ButtonedDialog', 'text!plugins/project-wizard/layer/device-select.html', './gcm' diff --git a/apps/ide/src/plugins/project-wizard/launcher.js b/apps/ide/src/plugins/project-wizard/launcher.js index 1927d3a4..8d7b5969 100644 --- a/apps/ide/src/plugins/project-wizard/launcher.js +++ b/apps/ide/src/plugins/project-wizard/launcher.js @@ -23,7 +23,7 @@ define([ 'webida-lib/util/logger/logger-client', 'webida-lib/util/path', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', './constants', './lib/util' ], function ( diff --git a/apps/ide/src/plugins/project-wizard/main.js b/apps/ide/src/plugins/project-wizard/main.js index ea10ffbd..e5dd4c69 100644 --- a/apps/ide/src/plugins/project-wizard/main.js +++ b/apps/ide/src/plugins/project-wizard/main.js @@ -36,7 +36,7 @@ define([ 'dojo/topic', 'lib/image-slide/sly.wrapper', 'showdown', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/app', 'webida-lib/util/logger/logger-client', 'webida-lib/util/notify', @@ -72,7 +72,7 @@ define([ var logger = new Logger(); logger.off(); - + var PW_MSG = { ERROR : 'Error', FAIL : 'Fail', @@ -643,6 +643,18 @@ define([ } function onCreateProject(event, prjName, selectedTemplate, options) { + + var item = selectedTemplate ? selectedTemplate : getSelectedTemplate(); + logger.log('onCreateProject', options); + var projectName = prjName; + var destSelect = targetPath; + + var srcFSPath = '/' + (item && item.template && item.template.path ? item.template.path : ''); + var srcFSPrjPath = srcFSPath + '/project'; + //logger.log('destFSPath', destSelect, projectName); + var destFSPath = Util.concatWFSPath([destSelect, projectName]); + var destFSCopyPath = Util.concatWFSPath(['wfs://', destFS, destSelect, projectName]); + function createProject() { function createDefaultProjectConf() { var name = projectName ? projectName : item.name; @@ -791,17 +803,6 @@ define([ }); } - var item = selectedTemplate ? selectedTemplate : getSelectedTemplate(); - logger.log('onCreateProject', options); - var projectName = prjName; - var destSelect = targetPath; - - var srcFSPath = '/' + (item && item.template && item.template.path ? item.template.path : ''); - var srcFSPrjPath = srcFSPath + '/project'; - //logger.log('destFSPath', destSelect, projectName); - var destFSPath = Util.concatWFSPath([destSelect, projectName]); - var destFSCopyPath = Util.concatWFSPath(['wfs://', destFS, destSelect, projectName]); - if (!isValidInfo()) { return; } else { diff --git a/apps/ide/src/plugins/project-wizard/plugin.js b/apps/ide/src/plugins/project-wizard/plugin.js index 37264305..5a3f0bae 100644 --- a/apps/ide/src/plugins/project-wizard/plugin.js +++ b/apps/ide/src/plugins/project-wizard/plugin.js @@ -25,7 +25,7 @@ define([ 'webida-lib/plugins/workbench/plugin', 'webida-lib/plugins/workspace/plugin', 'webida-lib/util/locale', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', './lib/util' ], function ( i18n, diff --git a/apps/ide/src/plugins/project-wizard/run-commands.js b/apps/ide/src/plugins/project-wizard/run-commands.js index d3bb2f5a..cf667d37 100644 --- a/apps/ide/src/plugins/project-wizard/run-commands.js +++ b/apps/ide/src/plugins/project-wizard/run-commands.js @@ -30,7 +30,7 @@ define([ 'dojox/grid/EnhancedGrid', 'dojox/grid/enhanced/plugins/IndirectSelection', 'webida-lib/app', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/util/path', 'webida-lib/widgets/dialogs/buttoned-dialog/ButtonedDialog', 'webida-lib/plugins/workspace/plugin', diff --git a/apps/ide/src/plugins/project-wizard/test-commands.js b/apps/ide/src/plugins/project-wizard/test-commands.js index 0a3f6d65..b43ca074 100644 --- a/apps/ide/src/plugins/project-wizard/test-commands.js +++ b/apps/ide/src/plugins/project-wizard/test-commands.js @@ -29,7 +29,7 @@ define([ 'webida-lib/util/logger/logger-client', 'webida-lib/util/path', 'webida-lib/plugins/workspace/plugin', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/views/view', 'webida-lib/widgets/views/viewmanager', 'text!./layer/test-layout.html', @@ -69,7 +69,6 @@ define([ var FRAMES = JSON.parse(frames).frames; function TestViewCommand() { - _self = this; this.$resolutions = null; this.url = null; } @@ -78,7 +77,6 @@ define([ var _super = TestViewCommand.prototype = new ViewCommand(VIEW_ID); // correct the constructor pointer because it points to ViewCommand TestViewCommand.prototype.constructor = TestViewCommand; - var _self = null; TestViewCommand.prototype.doTest = function () { var curDir = wv.getSelectedPath(); @@ -90,23 +88,24 @@ define([ }; // doTest TestViewCommand.prototype.onViewAppended = function () { + var self = this; _super.initView(tplToolbar, function (selectedProjectPath) { - if (_self.getProjectPath() === selectedProjectPath) { + if (self.getProjectPath() === selectedProjectPath) { return; } - _self.refreshView(); + self.refreshView(); // FIXME use directly `reg.byId('testTab').selectChild()` method Util.selectTab('testTab', 'testPreview'); }); - _self.$resolutions = $('#testResolutions'); - _self.$resolutions.multiselect({ + self.$resolutions = $('#testResolutions'); + self.$resolutions.multiselect({ includeSelectAllOption: false, enableCaseInsensitiveFiltering: false }); - _self.init(); // Event listeners should be attached once - _self.refreshView(); + self.init(); // Event listeners should be attached once + self.refreshView(); _super.getView().select(); }; diff --git a/apps/ide/src/plugins/uid-menu-items/plugin.js b/apps/ide/src/plugins/uid-menu-items/plugin.js index cb6702c8..049886ba 100644 --- a/apps/ide/src/plugins/uid-menu-items/plugin.js +++ b/apps/ide/src/plugins/uid-menu-items/plugin.js @@ -23,7 +23,7 @@ define([ 'dojo/i18n!./nls/resource', 'webida-lib/app-config', 'webida-lib/util/notify', - 'webida-lib/webida-0.3' + 'webida-lib/server-api' ], function ( i18n, appConfig, diff --git a/apps/ide/src/plugins/webida.editor.code-editor/CodeEditorViewer.js b/apps/ide/src/plugins/webida.editor.code-editor/CodeEditorViewer.js index 0808b887..6cc300c5 100644 --- a/apps/ide/src/plugins/webida.editor.code-editor/CodeEditorViewer.js +++ b/apps/ide/src/plugins/webida.editor.code-editor/CodeEditorViewer.js @@ -24,7 +24,7 @@ * @see CodeEditorPart * @since 1.3.0 * @author hw.shim@samsung.com - * @author sewon326.kim@samsung.com + * @author sewon326.kim@samsung.com * @author kyungmi.k@samsung.com * @author cg25.woo@samsung.com * @author h.m.kwon@samsung.com @@ -226,13 +226,13 @@ define([ { mode: ['html', 'xml'], checkToken: function (token) { - return _.contains(['tag', 'attribute', 'link'], token.type); + return _.includes(['tag', 'attribute', 'link'], token.type); } }, { mode: ['css'], checkToken: function (token) { - return _.contains(['tag', 'builtin', 'qualifier', 'property error', 'property'], token.type); + return _.includes(['tag', 'builtin', 'qualifier', 'property error', 'property'], token.type); } } ]; @@ -291,14 +291,14 @@ define([ } } function isAvailable(type, name) { - return _.contains(availables, type + '::' + name); + return _.includes(availables, type + '::' + name); } function cursorAtAutoHint(cm, modeName, cursor, rightToken) { var token = cm.getTokenAt(cursor); if (_.find(cursorAtAutoHintTokens, function (obj) { - return _.contains(obj.mode, modeName) && obj.checkToken(token); + return _.includes(obj.mode, modeName) && obj.checkToken(token); })) { return true; } @@ -493,7 +493,7 @@ define([ onChangeForAutoHintDebounced = _.debounce(function (cm, changeObj, lastCursor) { // TODO - limch - minimize addFile() call to WebWorker var editor = cm.__instance; - + if (editor._contentAssistDelegator) { var options = {}; options.async = true; @@ -635,7 +635,7 @@ define([ setMode : function (mode) { var that = this; - var assistDelegator = null; + var assistDelegator = null; that.promiseForSetMode = new Promise(function (resolve, reject) { if (mode === undefined || that.mode === mode) { resolve('no change'); @@ -739,31 +739,31 @@ define([ this.linters = {}; } this.linters[type] = option; - + var that = this; that.promiseForSetMode.then(function(){ var editor = that.editor; - if (editor._contentAssistDelegator) { + if (editor._contentAssistDelegator) { editor._contentAssistDelegator.execCommandForAll( 'setLinter', that, type, option); } - }); + }); }, __applyLinter : function () { - if (this.editor && this.linters && _.contains(['js', 'json', 'css', 'html'], this.mode)) { + if (this.editor && this.linters && _.includes(['js', 'json', 'css', 'html'], this.mode)) { if (this.linters[this.mode]) { this._gutterOn('CodeMirror-lint-markers'); - + var that = this; that.promiseForSetMode.then(function(){ var editor = that.editor; - if (editor._contentAssistDelegator) { + if (editor._contentAssistDelegator) { editor._contentAssistDelegator.execCommandForAll( 'applyLinter', - that.editor, + that.editor, that.mode); } }); @@ -905,7 +905,7 @@ define([ var editor = self.editor; self.focus(); self.promiseForSetMode.then(function(){ - if (editor._contentAssistDelegator) { + if (editor._contentAssistDelegator) { editor._contentAssistDelegator.execCommand('beautifyCode', editor); } }); diff --git a/apps/ide/src/plugins/webida.editor.code-editor/menus.js b/apps/ide/src/plugins/webida.editor.code-editor/menus.js index 3b24ace1..888d83e7 100644 --- a/apps/ide/src/plugins/webida.editor.code-editor/menus.js +++ b/apps/ide/src/plugins/webida.editor.code-editor/menus.js @@ -73,7 +73,7 @@ define([ blockCommentMenuItem.disabled = true; } if (delegator.canExecute('request')) { - delegator.exeCommand('request', widget, + delegator.execCommand('request', widget, { type: 'rename', newName: 'someName', fullDocs: true }, function (error) { if (!error) { @@ -198,7 +198,7 @@ define([ commentOutSelectionMenuItem.invisible = true; } if (delegator.canExecute('request')) { - delegator.exeCommand('request', widget, + delegator.execCommand('request', widget, { type: 'rename', newName: 'someName', fullDocs: true }, function (error) { if (!error) { diff --git a/apps/ide/src/plugins/webida.editor.text-editor/TextEditorViewer.js b/apps/ide/src/plugins/webida.editor.text-editor/TextEditorViewer.js index 4b79ba37..8e0d95cd 100644 --- a/apps/ide/src/plugins/webida.editor.text-editor/TextEditorViewer.js +++ b/apps/ide/src/plugins/webida.editor.text-editor/TextEditorViewer.js @@ -115,7 +115,7 @@ define([ } function isAvailable(type, name) { - return _.contains(availables, type + '::' + name); + return _.includes(availables, type + '::' + name); } var eventTransformers = { @@ -319,7 +319,7 @@ define([ cm.indentLine(e.from.line + i); } } - }); + }); }, synchronizeWidgetModel: function (recentViewer) { @@ -471,7 +471,7 @@ define([ setSelection: function (anchor, head) { this.cursor = head; this.addDeferredAction(function (self) { - self.editor.getDoc().setSelection(eventTransformers.wrapperLoc2cmLoc(anchor), + self.editor.getDoc().setSelection(eventTransformers.wrapperLoc2cmLoc(anchor), eventTransformers.wrapperLoc2cmLoc(head)); }); if (!this.editor) { @@ -526,7 +526,7 @@ define([ } }); }, - + /** * Highlights strings matching given query with query options * Example: viewer.setHighlight('string',{caseSensitive: false, regexp: false, wholeWord: false}); @@ -552,7 +552,7 @@ define([ } this.theme = theme; if (theme === 'codemirror-default') { - theme = this.theme = 'default'; + theme = this.theme = 'default'; this.addDeferredAction(function (self) { self.editor.setOption('theme', self.theme); }); @@ -572,7 +572,7 @@ define([ break; } loadCSSList([require.toUrl(csspath)], function () { - addAvailable('theme', theme); + addAvailable('theme', theme); self.addDeferredAction(function (self) { self.editor.setOption('theme', self.theme); }); @@ -644,16 +644,16 @@ define([ _gutterOn: function (gutterName) { if (this.editor) { var gutters = this.editor.getOption('gutters'); - if (!_.contains(gutters, gutterName)) { + if (!_.includes(gutters, gutterName)) { var i, newgutters = []; var order = ['CodeMirror-linenumbers', 'CodeMirror-lint-markers', 'CodeMirror-foldgutter']; for (i = 0; i < order.length; i++) { - if (_.contains(gutters, order[i]) || order[i] === gutterName) { + if (_.includes(gutters, order[i]) || order[i] === gutterName) { newgutters.push(order[i]); } } for (i = 0; i < gutters.length; i++) { - if (!_.contains(order, gutters[i])) { + if (!_.includes(order, gutters[i])) { newgutters.push(gutters[i]); } } @@ -806,7 +806,7 @@ define([ if (codeFolding) { var self = this; loadCSSList([require.toUrl('./css/codefolding.css')], function () { - require(['external/codemirror/addon/fold/foldcode', 'external/codemirror/addon/fold/foldgutter', + require(['external/codemirror/addon/fold/foldcode', 'external/codemirror/addon/fold/foldgutter', 'external/codemirror/addon/fold/brace-fold'], function () { self.addDeferredAction(function (self) { self._gutterOn('CodeMirror-foldgutter'); @@ -1320,14 +1320,14 @@ define([ replace: function () { this.addDeferredAction(function (self) { - var editor = self.editor; + var editor = self.editor; editor.execCommand('replace'); }); }, find: function () { this.addDeferredAction(function (self) { - var editor = self.editor; + var editor = self.editor; editor.execCommand('find'); }); }, @@ -1428,7 +1428,7 @@ define([ var merge = function (current, processed, keymap) { var curKeyMap = codemirror.keyMap[current]; keymap = _.extend(keymap, curKeyMap); - if (curKeyMap.fallthrough && !_.contains(processed, curKeyMap.fallthrough)) { + if (curKeyMap.fallthrough && !_.includes(processed, curKeyMap.fallthrough)) { processed.push(curKeyMap.fallthrough); return merge(curKeyMap.fallthrough, processed, keymap); } else { diff --git a/apps/ide/src/plugins/webida.ide.project-management.run/run-configuration-manager.js b/apps/ide/src/plugins/webida.ide.project-management.run/run-configuration-manager.js index ee154c12..67250f86 100644 --- a/apps/ide/src/plugins/webida.ide.project-management.run/run-configuration-manager.js +++ b/apps/ide/src/plugins/webida.ide.project-management.run/run-configuration-manager.js @@ -110,6 +110,8 @@ define([ if (err) { next(err); } else if (!exist) { + runConfigurationFileCache = '{}'; + runConfigurations = []; next(locale.formatMessage('messageNotExist', {target: PATH_RUN_CONFIG})); } else { next(); diff --git a/apps/ide/src/plugins/webida.ide.project.deploy/commands.js b/apps/ide/src/plugins/webida.ide.project.deploy/commands.js index 1024cc9f..d159d783 100644 --- a/apps/ide/src/plugins/webida.ide.project.deploy/commands.js +++ b/apps/ide/src/plugins/webida.ide.project.deploy/commands.js @@ -31,7 +31,7 @@ define([ 'webida-lib/util/logger/logger-client', 'webida-lib/util/notify', 'webida-lib/util/path', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', './content-view-controller', './workspace-view-controller', 'text!./layout/deploy-layout.html', diff --git a/apps/ide/src/plugins/webida.ide.project.deploy/content-view-controller.js b/apps/ide/src/plugins/webida.ide.project.deploy/content-view-controller.js index 5f6be221..6958527a 100644 --- a/apps/ide/src/plugins/webida.ide.project.deploy/content-view-controller.js +++ b/apps/ide/src/plugins/webida.ide.project.deploy/content-view-controller.js @@ -39,7 +39,7 @@ define([ 'webida-lib/util/locale', 'webida-lib/util/logger/logger-client', 'webida-lib/util/notify', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/dialogs/file-selection/FileSelDlg3States', // FileDialog 'text!./layout/app-information.html' ], function ( diff --git a/apps/ide/src/plugins/webida.preference/pages/SimplePage.js b/apps/ide/src/plugins/webida.preference/pages/SimplePage.js index 1bce5f95..0b8eada8 100644 --- a/apps/ide/src/plugins/webida.preference/pages/SimplePage.js +++ b/apps/ide/src/plugins/webida.preference/pages/SimplePage.js @@ -194,7 +194,7 @@ define([ }); var currentId = value; - var currentValue = _.findWhere(dataSource, {id: currentId}); + var currentValue = _.find(dataSource, {id: currentId}); if (currentValue) { currentValue = currentValue.name; } else { @@ -210,7 +210,7 @@ define([ maxHeight: 300, style: 'margin-top: 5px; margin-bottom: 5px; margin-left: 5px;', onChange: function (value) { - var item = _.findWhere(dataSource, {name: value}); + var item = _.find(dataSource, {name: value}); if (item) { self.store.setValue(key, item.id); } diff --git a/apps/ide/src/plugins/webida.terminal.restrict/plugin.js b/apps/ide/src/plugins/webida.terminal.restrict/plugin.js index bccb8b7a..2d2ae24a 100644 --- a/apps/ide/src/plugins/webida.terminal.restrict/plugin.js +++ b/apps/ide/src/plugins/webida.terminal.restrict/plugin.js @@ -33,7 +33,7 @@ define([ 'dojo/i18n!./nls/resource', 'webida-lib/app', // ide 'webida-lib/util/locale', - 'webida-lib/webida-0.3', // webida + 'webida-lib/server-api', // webida 'webida-lib/widgets/views/view', // View 'dojo/text!./layout/terminal.html' ], function ( diff --git a/apps/ide/src/plugins/webida.terminal/plugin.js b/apps/ide/src/plugins/webida.terminal/plugin.js index 5262ed82..59d4ca25 100644 --- a/apps/ide/src/plugins/webida.terminal/plugin.js +++ b/apps/ide/src/plugins/webida.terminal/plugin.js @@ -41,7 +41,7 @@ define([ 'webida-lib/plugins/workbench/plugin', // workbench 'webida-lib/util/locale', 'webida-lib/util/logger/logger-client', // Logger - 'webida-lib/webida-0.3', // webida + 'webida-lib/server-api', // webida 'webida-lib/widgets/views/view', // View 'dojo/text!./layout/terminal.html', // terminalHtml 'xstyle/css!./style/terminal.css' diff --git a/bower.json b/bower.json index 300a3b75..689b808d 100644 --- a/bower.json +++ b/bower.json @@ -6,35 +6,33 @@ "bower_components" ], "dependencies": { + "async": "~1.3.0", + "bootstrap-tooltip-popover": "twbs/bootstrap#ebc69356637143c938c07904d10928c11e742196", + "calcium": "webida/calcium#415ebc22cc6a09457bb9e7445f149061787920fe", "codemirror": "webida/codemirror#b1738f33d1920d7d3e3423dc96c1878097d01d24", "dgrid": "0.3.11", - "put-selector": "~0.3.6", - "xstyle": "~0.3.1", - "async": "~1.3.0", + "dijit": "dojo/dijit#~1.10.4", + "dojo": "dojo/dojo#~1.10.4", + "dojox": "dojo/dojox#~1.10.4", + "eventEmitter": "~4.2.11", + "es6-shim": "~0.33.9", + "jquery": "~2.1.4", + "jquery-cookie" : "carhartl/jquery-cookie#faa09dc38bd3c791212e8fca67ee661af55fa530", "js-beautify": "~1.5.10", - "lodash": "~3.10.0", + "lodash": "~4.14.0", "moment": "~2.10.3", - "URIjs": "~1.15.2", - "requirejs": "~2.1.18", + "put-selector": "~0.3.6", "qunit": "~1.18.0", - "qunit-composite": "git://github.com/JamesMGreene/qunit-composite#~1.1.0", - "toastr": "~2.1.1", + "qunit-composite": "JamesMGreene/qunit-composite#~1.1.0", + "requirejs": "~2.1.18", "showdown": "~1.1.0", - "socket.io-client": "git://github.com/automattic/socket.io-client#~1.3.5", - "eventEmitter": "~4.2.11", - "bootstrap-treeview": "~1.2.0", - "calcium": "git://github.com/webida/calcium#415ebc22cc6a09457bb9e7445f149061787920fe", - "tern": "git://github.com/webida/tern#ab9f92a23d9f8d09071c3549b33ff40dcfb27ed3", "smalot-bootstrap-datetimepicker": "~2.3.4", - "es6-shim": "~0.33.9", - "term.js": "git://github.com/chjj/term.js.git#~v0.0.7", - "dojo": "dojo/dojo#~1.10.4", - "dijit": "dojo/dijit#~1.10.4", - "dojox": "dojo/dojox#~1.10.4", - "util": "dojo/util#~1.10.4", - "jquery": "~2.1.4", - "ladda": "git://github.com/hakimel/Ladda#ec575edabd0602ed303978148df7d5e731014010", - "bootstrap-tooltip-popover": "git://github.com/twbs/bootstrap#ebc69356637143c938c07904d10928c11e742196", - "jquery-cookie" : "git://github.com/carhartl/jquery-cookie#faa09dc38bd3c791212e8fca67ee661af55fa530" + "ladda": "hakimel/Ladda#ec575edabd0602ed303978148df7d5e731014010", + "term.js": "chjj/term.js#~v0.0.7", + "tern": "webida/tern#ab9f92a23d9f8d09071c3549b33ff40dcfb27ed3", + "toastr": "~2.1.1", + "URIjs": "~1.18.1", + "webida-service-client": "webida/webida-service-client#~0.8.1", + "xstyle": "~0.3.1" } } diff --git a/common/src/webida/FSCache-0.1.js b/common/src/webida/FSCache-0.1.js index b416b217..32bfb86d 100644 --- a/common/src/webida/FSCache-0.1.js +++ b/common/src/webida/FSCache-0.1.js @@ -14,7 +14,7 @@ * limitations under the License. */ -define(['webida-lib/webida-0.3', +define(['webida-lib/server-api', 'webida-lib/util/arrays/SortedArray', 'webida-lib/util/path', 'external/lodash/lodash.min', @@ -50,12 +50,16 @@ function (webida, SortedArray, pathUtil, _, URI, declare, topic) { var TYPE_DIRECTORY = 'dir'; var TYPE_UNKNOWN = 'unknown'; + // due to cyclc dependencies among FSNode/File/Directory class + /*jshint latedef: false */ function FSCache(fsURLArg, dirsToCacheArg) { //******************************* // class FSCacheInner //******************************* + var fsCache; + //-------------------------- // private fields of FSCacheInner //-------------------------- @@ -66,2154 +70,2158 @@ function (webida, SortedArray, pathUtil, _, URI, declare, topic) { var mount; var root; - var FSCacheInner = declare(null, { - - constructor : function () { - // dirsToCaches_ must be an array of valid absolute paths - if (!dirsToCacheArg.every(function (dir) { - return isValidAbsPath(dir) && pathUtil.isDirPath(dir); - })) { - - throw new Error('The second argument must be an ' + - 'array of absolute directory paths'); - } - // they must represent disjoin set of directories. - var len = dirsToCacheArg.length; - for (var i = 0; i < len - 1; i++) { - var di = dirsToCacheArg[i]; - for (var j = i + 1; j < len; j++) { - if (di.indexOf(dirsToCacheArg[j]) === 0) { - throw new Error('Directories to cache are not disjoint'); - } - } - } + //******************************* + // inner class of FSCacheInner: FSNode + //******************************* - // Now, it's ok to build an of object of FSCacheInner + var FSNode = declare(null, { - // set private fields - fsURL = fsURLArg; - fsURLParsed = parseWFSURL(fsURL); - dirsToCache = dirsToCacheArg; - publishing = false; - mount = webida.fs.mount(fsURL); - root = new Directory(null, ''); // no parent, empty name - root.getByAbsPath = function (absPath) { - //console.log('hina temp: absPath = ' + absPath); - if (absPath.indexOf('/') === 0) { - return absPath === '/' ? this : this.getByRelPath(absPath.substr(1)); - } else { - console.error('Unreachable: cannot get "' + absPath + '" under "/"'); - throw new Error('Unreachable: cannot get "' + absPath + '" under "/"'); - } - }; - root.putByAbsPath = function (absPath, caseStr) { - if (absPath.indexOf('/') === 0 && absPath.length > 1) { - return this.putByRelPath(absPath.substr(1), caseStr); - } else { - console.error('Unreachable: cannot put "' + absPath + '" under "/"'); - throw new Error('Unreachable: cannot put "' + absPath + '" under "/"'); - } - }; - //root.show(0); + constructor : function (parent, name) { + this.parent = parent; // null iff this is the root node + this.name = name; + this.metadata = {}; + this.metadataInvalidated = {}; }, - getSummary : function () { - return root.getSummary(); - }, + setMetadata: function (key, value, caseStr) { + var origValue = this.metadata[key]; + this.metadata[key] = value; + this.metadataInvalidated[key] = false; - start : function (summary) { - if (summary) { - root.restoreFromSummary(summary); - root.fetchedSubnodes = false; // TODO: generalize this. + var path = this.getPath(); + switch (caseStr) { + case 'fetched': + onMetadataFetched(path, key); + break; + case 'written': + onMetadataSet(path, key, value !== origValue); + break; + case 'refreshed': + onMetadataRefreshed(path, key); + break; + default: + console.assert(false, 'Unreachable'); } - - // add nodes for cache roots - dirsToCache.forEach(function (dir) { - root.putByAbsPath(dir, 'cache-root'); - }); - - //console.log('Starting FS-Cache.'); - //root.show(0); - publishing = true; - - subscribeToNotificationsOnFS(this); }, - invalidateFileContents: function (path) { - if (isValidAbsPath(path) && withinCache(path)) { - var node = root.getByAbsPath(path); - if (!node) { - throw new Error('Not in the cache: ' + path); + refreshMetadata: function (key) { + var self = this; + Object.keys(this.metadata).forEach(function (k) { + if (key === undefined || key === k) { + var origVal = self.metadata[k]; + var path = self.getPath(); + mount.getMeta(path, k, function (err, newVal) { + if (err) { + console.error('Cannot get metadata ' + k + ' of ' + path + + ' from server: ' + err); + } else { + if (origVal !== newVal) { + this.setMetadata(k, newVal, 'refreshed'); + } + } + }); } + }); + }, - node.invalidateFileContents(); + getParentPath : function () { + return this.parent ? this.parent.getPath() : EMPTY_PATH; + }, - } else { - throw new Error('The path "' + path + '" is not a valid path being cached'); + getPath : function () { + if (!this._path) { + // memoize + this._path = this.computePath(); } + return this._path; }, - invalidateMetadata: function (path, key) { - if (isValidAbsPath(path) && withinCache(path)) { - var node = root.getByAbsPath(path); - if (!node) { - throw new Error('Not in the cache: ' + path); - } + computePath : function () { + return this.getParentPath() + this.name; + }, - node.invalidateMetadata(key); + getType : function () { + return TYPE_UNKNOWN; + }, + detach : function (movedTo) { + if (this.parent) { + var detached = this.parent.removeSubnode(this.name, movedTo); + if (this !== detached) { + throw new Error('Detached node is wrong.'); + } } else { - throw new Error('The path "' + path + '" is not a valid path being cached'); + throw new Error('Cannot detach the root node'); } }, - refreshHierarchy: function (path, options, cb) { - if (isValidAbsPath(path) && withinCache(path)) { - var node = root.getByAbsPath(path); - if (!node) { - throw new Error('Not in the cache: ' + path); - } + satisfyingCond : function (cond) { - var level; - if (node.getType() === TYPE_FILE) { - node = node.parent; - level = 1; - } else { - if (options && 'level' in options) { - level = Math.floor(options.level); - } else { - throw new Error('Level must be given in the options argument'); - } + if (cond.types) { + if (cond.types.indexOf(this.getType()) < 0) { + return false; } + } - cb = cb || doNothing; - node.refreshHierarchy(level, cb); + return true; + }, - } else { - throw new Error('The path "' + path + '" is not a valid path being cached'); + collectNodes : function (arr, cond) { + if (this.satisfyingCond(cond)) { + arr.push(this); } }, - refreshFileContents: function (path, options) { - if (isValidAbsPath(path) && withinCache(path)) { - var node = root.getByAbsPath(path); - if (!node) { - throw new Error('Not in the cache: ' + path); - } + getListInfo : function () { + return { + name : this.name, + isDirectory : this instanceof Directory, + isFile : this instanceof File + }; + }, - if (node.getType() === TYPE_FILE) { // only for files - node.refreshFileContents(); - } else { - var level; - if (options && 'level' in options) { - level = Math.floor(options.level); - } else { - throw new Error('Level must be given in the \'options\' argument'); - } - node.refreshFileContents(level); // - } - } else { - throw new Error('The path "' + path + '" is not a valid path being cached'); + show : function (level) { // for debugging + var arr = []; + for (var i = 0; i < level; i++) { + arr.push('| '); } + + arr.push(this.name); + console.log(arr.join('')); }, - refreshMetadata: function (path, key, options) { - if (isValidAbsPath(path) && withinCache(path)) { - var node = root.getByAbsPath(path); - if (!node) { - throw new Error('Not in the cache: ' + path); - } + invalidateFileContents: function () { + console.error('assertion fail: unreachable'); + }, - if (node.getType() === TYPE_FILE) { // only for files - node.refreshMetadata(key); - } else { - var level; - if (options && 'level' in options) { - level = Math.floor(options.level); - } else { - throw new Error('Level must be given in the \'options\' argument'); + invalidateMetadata: function (key) { + var keys = Object.keys(this.metadataInvalidated); + var path = this.getPath(); + if (key === undefined) { + keys.forEach(function (k) { + if (this.metadata[k] !== undefined && + !this.metadataInvalidated[k]) { + this.metadataInvalidated[k] = true; + onMetadataInvalidated(path, k); } - node.refreshMetadata(level, key); // + }); + } else if (keys.indexOf(key) >= 0) { + if (this.metadata[key] !== undefined && + !this.metadataInvalidated[key]) { + this.metadataInvalidated[key] = true; + onMetadataInvalidated(path, key); } } else { - throw new Error('The path "' + path + '" is not a valid path being cached'); + throw new Error('Metadata ' + key + ' is not set for "' + + this.getPath() + '"'); } - }, + } - // convenience API: refresh all (hierarchy, file contents, and metadata) - refresh : function (path, options, cbWhenHierarchyDone) { - var opt = { level: 1 }; - if (options) { - _.extend(opt, options); - } + }); - var self = this; - this.refreshHierarchy(path, opt, function () { - if (cbWhenHierarchyDone) { - cbWhenHierarchyDone(); - } + //******************************* + // inner class of FSCacheInner: File + //******************************* - self.refreshFileContents(path, opt); - self.refreshMetadata(path, undefined/* every key */, opt); - }); + var File = declare(FSNode, { + constructor : function (/*parent, name*/) { + this.contentInvalidated = false; }, - queryPaths : function (dirPath, condition) { - if (isValidAbsPath(dirPath) && pathUtil.isDirPath(dirPath) && withinCache(dirPath)) { - var dirNode = root.getByAbsPath(dirPath); - if (!dirNode) { - return null; - } + invalidateFileContents: function () { + if (this.content !== undefined && !this.contentInvalidated) { + this.contentInvalidated = true; + onFileInvalidated(this.getPath()); + } + }, - var nodeArr = []; - dirNode.collectNodes(nodeArr, condition); - return nodeArr.map(function (node) { - return node.getPath(); + refreshFileContents : function () { + + // NOTE: refreshing contents is possible for invalidated contents too + + var origContent = this.content; + if (origContent !== undefined) { + var path = this.getPath(); + var self = this; + mount.readFile(path, function (err, newContent) { + if (err) { + console.log('Cannot get content of ' + path + + ' from server. cannot refresh the file content (' + + err + ')'); + } else { + if (origContent !== newContent) { + self.setContent(newContent, 'refreshed'); + } + } }); + } + }, - } else { - throw new Error('The path "' + dirPath + - '" does not represent a directory being cached'); + setContent : function (content, caseStr) { + var origContent = this.content; + this.content = content; + this.contentInvalidated = false; + + var path = this.getPath(); + switch (caseStr) { + case 'fetched': + onFileFetched(path); + break; + case 'written': + onFileWritten(path, content === undefined || content !== origContent); + break; + case 'refreshed': + onFileRefreshed(path); + break; + default: + console.assert(false, 'Unreachable'); } }, - //--------------------------- - // FS API wrapper - methods WITH update - //--------------------------- + getType : function () { + return TYPE_FILE; + }, - // copy - copy : function (src, dst, recursive, cb) { + getSummary : function () { + return this.name; + } + }); - var srcNode, dstNode; - function checkValidCopy() { - var ret = checkTargetPath(src, true, true); - if (isError(ret)) { - return ret; - } - srcNode = ret; + //******************************* + // inner class of FSCacheInner: Directory + //******************************* - ret = checkTargetPath(dst, null); - if (isError(ret)) { - return ret; - } - dstNode = ret; + var Directory = declare(FSNode, { + constructor : function (/*parent, name*/) { + this.dirs = new SortedArray('name'); + this.files = new SortedArray('name'); + }, - if (src === dst) { - return 'Source and destination paths are identical: ' + src + '.'; - } + invalidateFileContents: function () { + this.dirs.forEach(function (dir) { + dir.invalidateFileContents(); + }); + this.files.forEach(function (file) { + file.invalidateFileContents(); + }); + }, - if (dst.indexOf(src + '/') === 0) { - return 'Cannot copy a directory into its descendant'; - } + invalidateMetadata: function (key) { + FSNode.prototype.invalidateMetadata.call(this, key); + this.dirs.forEach(function (dir) { + dir.invalidateMetadata(key); + }); + this.files.forEach(function (file) { + file.invalidateMetadata(key); + }); + }, - if (srcNode instanceof Directory && !recursive) { - return 'Copying a directory with recursive option unset is disallowed.'; - } + refreshFileContents : function (level) { + if (typeof level !== 'number') { // TODO: remove this check when stabilized + throw new Error('assertion fail: unrechable'); + } - var divided = pathUtil.dividePath(dst); - if (root.getByAbsPath(divided[0]) === null) { - return 'Destination directory "' + divided[0] + '" does not exist.'; - } + if (level) { + this.dirs.forEach(function (dir) { + dir.refreshFileContents(level - 1); + }); + this.files.forEach(function (file) { + file.refreshFileContents(); + }); + } + }, - return null; + refreshMetadata: function (level, key) { + if (typeof level !== 'number') { // TODO: remove this check when stabilized + throw new Error('assertion fail: unrechable'); } - function callback(err) { + FSNode.prototype.refreshMetadata.call(this, key); + if (level) { + this.dirs.forEach(function (dir) { + dir.refreshMetadata(level - 1, key); + }); + this.files.forEach(function (file) { + file.refreshMetadata(key); + }); + } + }, - function handleCopySuccess(isDir) { - console.assert(srcNode === root.getByAbsPath(src), - 'A source node has been modified during a ' + - 'copy request to server: ' + src); - console.assert(dstNode === root.getByAbsPath(dst), - 'A destination node has been modified during a ' + - 'copy request to server: ' + dst); + computePath : function () { + return this.getParentPath() + this.name + '/'; + }, - var t = dst + (isDir ? '/' : ''); - var addedNodes = root.putByAbsPath(t, 'copied'); - if (!addedNodes) { // because the target has been already present. - fsCache.refresh(t, { level: -1 }); - } - cb(null); - } + putByRelPath : function (relPath, caseStr) { + console.assert(relPath, + 'Directory.putByRelPath() was called with a falsy argument'); - if (err) { - /* TODO: continue 'serverless operations' - if (err === 'server unreachable' && srcNode && !dstNode) { + var i = relPath.indexOf('/'); + if (i < 0) { + // base case + var file = this.putSubnode(relPath, false, caseStr); + return (file ? [file] : null); + } else { + console.assert(i > 0, 'i must be a positive integer'); + var subdirName = relPath.substring(0, i); + var subdir = this.putSubnode(subdirName, true, caseStr); + var nextPath = relPath.substr(i + 1); + if (nextPath) { + if (subdir) { + // newly added + var addedArr = subdir.putByRelPath(nextPath, caseStr); + addedArr.unshift(subdir); + return addedArr; } else { - cb(err); - return; + // already there + subdir = this.getSubnode(subdirName); + return subdir.putByRelPath(nextPath, caseStr); } - */ - cb(err); } else { - if (srcNode) { - handleCopySuccess(srcNode instanceof Directory); - } else { - mount.isDirectory(dst, function (err, isDir) { - if (err) { - cb(err); - } else { - handleCopySuccess(isDir); - } - }); - } + // base case + return (subdir ? [subdir] : null); } } + }, - if (typeof recursive === 'function') { - cb = recursive; - recursive = false; // false is default - } + // If a subnode exists with that name and type, then do nothing and return null. + // Otherwise, create the subnode, add it, and return the added subnode. + putSubnode : function (name, isDir, caseStr) { + console.assert(name); + console.assert(name.indexOf('/') === -1); - src = pathUtil.detachSlash(src); - var err = checkValidCopy(); - if (err) { - setTimeout(cb.bind(null, err), 0); + var subnode = this.getSubnode(name); + if (subnode && (subnode.isInstanceOf(Directory) === isDir)) { + return null; } else { - mount.copy(src, dst, recursive, withinCache(dst) ? callback : cb); + if (subnode) { + console.warn('A subnode with the same name "' + name + + '" but with different type was detected while putting a ' + + (isDir ? 'directory' : 'file') + ' to "' + + this.getPath() + '"'); + return null; + } else { + var added = this.addSubnode(name, isDir, caseStr); + return added; + } } }, - // move - move : function (src, dst, cb) { - - var srcNode, dstNode; - function checkValidMove() { - var ret = checkTargetPath(src, true, true); - if (isError(ret)) { - return ret; + addSubnode : function (name, isDir, caseStr) { + if (this.getSubnode(name)) { // TODO: remove this check if code is stabilized + console.error('Unreachable: Cannot overwrite existing subnode ' + name + + ' of ' + this.getPath() + ' by addSubnode()'); + throw new Error('Unreachable: Cannot overwrite existing subnode ' + name + + ' of ' + this.getPath() + ' by addSubnode()'); + } else { + var C = isDir ? Directory : File; + var subnode = new C(this, name); + if (isDir) { + this.dirs.add(subnode); + } else { + this.files.add(subnode); } - srcNode = ret; - ret = checkTargetPath(dst, null); - if (isError(ret)) { - return ret; - } - dstNode = ret; - - if (src === dst) { - return 'Source and destination paths are identical: ' + src + '.'; - } - - if (dst.indexOf(src + '/') === 0) { - return 'Cannot move a directory into its descendant'; + var maybeCreated; + switch (caseStr) { + case 'cache-root': + case 'inferred': + case 'fetched': + case 'restored': + maybeCreated = false; + break; + case 'copied': + case 'moved': + case 'dir-created': + case 'zip-created': + case 'zip-extracted': + case 'file-written': + case 'refreshed': + maybeCreated = true; + break; + default: + console.assert(false, 'Unreachable'); } - var divided = pathUtil.dividePath(dst); - if (root.getByAbsPath(divided[0]) === null) { - return 'Destination directory "' + divided[0] + '" does not exist.'; - } + onNodeAdded(this.getPath(), name, subnode.getType(), maybeCreated, caseStr === 'moved'); - return null; + return subnode; } + }, - function callback(err) { - function handleMoveSuccess(isDir) { - console.assert(srcNode === root.getByAbsPath(src), - 'A source node has been modified ' + - 'duirng a move request to server: ' + src); - console.assert(dstNode === root.getByAbsPath(dst), - 'A destination node has been modified ' + - 'duirng a move request to server: ' + dst); + getByRelPath: function (relPath) { + //console.log('hina temp: relPath = ' + relPath); + console.assert(relPath, + 'Directory.getByRelPath() was called ' + + 'with falsy argument'); - if (withinCache(dst)) { - var t = dst + (isDir ? '/' : ''); - var addedNodes = root.putByAbsPath(t, 'moved'); - if (!addedNodes) { // because the target has been already present. - fsCache.refresh(t, { level: -1 }); + var i = relPath.indexOf('/'); + if (i < 0) { + return this.getSubnode(relPath); + } else { + console.assert(i > 0, + 'Directory.getByRelPath() was called ' + + 'with an absolute path: ' + relPath); + var nextPath; + var subnodeName = relPath.substring(0, i); + var subnode = this.getSubnode(subnodeName); + if (subnode) { + nextPath = relPath.substr(i + 1); + if (nextPath) { + if (subnode instanceof Directory) { + return subnode.getByRelPath(nextPath); + } else { + return null; } - } - if (srcNode) { - srcNode.detach(withinCache(dst) ? dst : undefined); - } - cb(null); - } - - if (err) { - cb(err); - } else { - if (srcNode) { - handleMoveSuccess(srcNode instanceof Directory); } else { - mount.isDirectory(dst, function (err, isDir) { - if (err) { - cb(err); - } else { - handleMoveSuccess(isDir); - } - }); + return subnode; } + } else { + return subnode; } } + }, - src = pathUtil.detachSlash(src); - var err = checkValidMove(); - if (err) { - setTimeout(cb.bind(null, err), 0); + getSubnode : function (name) { + var queried = { name: name }; + var ret = this.dirs.query(queried) || this.files.query(queried); + if (ret) { + return ret; } else { - mount.move(src, dst, (srcNode || withinCache(dst)) ? callback : cb); + return this.fetchedSubnodes ? null : undefined; } }, - // createDirectory - createDirectory : function (target, recursive, cb) { - var targetNode; - function checkValidCreateDirectory() { - var ret = checkTargetPath(target, false, true); - if (isError(ret)) { - return ret; - } - targetNode = ret; - - if (!recursive) { - var parentPath = pathUtil.dividePath(target)[0]; - var parentNode = root.getByAbsPath(parentPath); - if (parentNode === null) { - return 'Parent directory "' + parentPath + - '" does not exist and the recursive option is not set.'; - } - } + removeSubnode : function (name, movedTo) { + var ret = this.getSubnode(name); + if (ret) { + var isDir = ret instanceof Directory; + var arr = isDir ? this.dirs : this.files; + var i = arr.indexOf(ret); + Array.prototype.splice.call(arr, i, 1); - return null; + onNodeDeleted(this.getPath(), name, ret.getType(), movedTo); } + return ret; + }, - function callback(err) { - console.assert(targetNode === root.getByAbsPath(target), - 'A target node has been modified during a ' + - 'createDirectory request to server: ' + target); - if (err) { - cb(err); - } else { - root.putByAbsPath(target + '/', 'dir-created'); - cb(null); - } - } + getType : function () { + return TYPE_DIRECTORY; + }, - if (typeof recursive === 'function') { - cb = recursive; - recursive = false; // false is default - } + isEmpty : function () { + return (this.dirs.length === 0) && (this.files.length === 0); + }, - target = pathUtil.detachSlash(target); - var err = checkValidCreateDirectory(); - if (err) { - setTimeout(cb.bind(null, err), 0); - } else { - mount.createDirectory(target, recursive, withinCache(target) ? callback : cb); - } + updateSubnodes : function (stats, isDir, caseStr) { + var subnodes = isDir ? this.dirs : this.files; + var names = subnodes.map(getName); + var newNames = stats.map(getName); + + //console.log('hina temp: names = ' + names); + //console.log('hina temp: newNames = ' + newNames); + var toAdd = _.difference(newNames, names); + var toDel = _.difference(names, newNames); + //console.log('hina temp: toAdd = ' + toAdd); + //console.log('hina temp: toDel = ' + toDel); + var self = this; + toDel.forEach(function (name) { + self.removeSubnode(name); + }); + toAdd.forEach(function (name) { + self.addSubnode(name, isDir, caseStr); + }); }, - createZip : function (sources, target, cb) { - var targetNode; - function checkValidCreateZip() { - if (sources.some(function (path) { return !isValidAbsPath(path); })) { - return 'List of sources contains a path which is not a valid absolute path.'; - } + // refresh hierarchy level by level + refreshHierarchy : function (level, doWhenAllDone) { + //console.log('hina temp: entering refreshHierarchy dirPath = ' + this.getPath()); - var ret = checkTargetPath(target, false); - if (isError(ret)) { - return ret; - } - targetNode = ret; + // NOTE: getByAbsPath() must be invoked on the root. + // Nodes (except for the root) can be detached at any time during an + // asynchronous method call. - return null; - } + if (level && this.fetchedSubnodes) { + var dirPath = this.getPath(); + var self = this; + mount.list(dirPath, false, function (err, stats) { - function callback(err) { - console.assert(targetNode === root.getByAbsPath(target), - 'A target node has been modified during a ' + - 'createZip request to server: ' + target); - if (err) { - cb(err); - } else { - root.putByAbsPath(target, 'zip-created'); - cb(null); - } - } + var subnodeTypesDone = 0; + function oneTypeDone() { + subnodeTypesDone++; + //console.log('hina temp: oneTypeDone for ' + dirPath + ' ' + subnodeTypesDone + ' time '); + if (subnodeTypesDone === 2) { // two types (dirs and files) + //console.log('hina temp: ' + dirPath + ' is done'); + doWhenAllDone(); + } + } - var err = checkValidCreateZip(); - if (err) { - setTimeout(cb.bind(null, err), 0); - } else { - mount.createZip(sources, target, withinCache(target) ? callback : cb); - } - }, + if (err) { + console.warn('Error: FileSystem.list failed while refreshing "' + + dirPath + '" (' + err + ')'); + doWhenAllDone(); + } else { + var newDirs = stats.filter(isDir); + self.updateSubnodes(newDirs, true, 'refreshed'); - // delete - 'delete' : function (target, recursive, cb) { - var targetNode; - function callback(err) { - console.assert(targetNode === root.getByAbsPath(target), - 'A target node has been modified duirng a ' + - 'delete request to server: ' + target); - if (err) { - cb(err); - } else { - if (targetNode) { - targetNode.detach(); + var subdirsToRefresh = self.dirs.length; + var subdirsRefreshed = 0; + if (subdirsToRefresh) { + self.dirs.forEach(function (dir) { + dir.refreshHierarchy(level - 1, function () { + subdirsRefreshed++; + if (subdirsRefreshed === subdirsToRefresh) { + //console.log('hina temp: subdirs of ' + dirPath + ' are done'); + oneTypeDone(); + } + }); + }); + } else { + oneTypeDone(); + } + + var newFiles = stats.filter(isFile); + self.updateSubnodes(newFiles, false, 'refreshed'); + oneTypeDone(); } - cb(null); - } + }); + } else { + doWhenAllDone(); } - if (typeof recursive === 'function') { - cb = recursive; - recursive = false; // false is default - } - var ret = checkTargetPath(target, true, true); - if (isError(ret)) { - setTimeout(cb.bind(null, ret), 0); + }, + + collectNodes : function (arr, cond) { + FSNode.prototype.collectNodes.call(this, arr, cond); // super call + this.dirs.forEach(function (dir) { + dir.collectNodes(arr, cond); + }); + this.files.forEach(function (file) { + file.collectNodes(arr, cond); + }); + }, + + list : function () { + if (this.fetchedSubnodes) { + var arr = []; + + this.dirs.forEach(function (subnode) { + arr.push(subnode.getListInfo()); + }); + + this.files.forEach(function (subnode) { + arr.push(subnode.getListInfo()); + }); + + return arr; } else { - targetNode = ret; - mount.delete(target, recursive, withinCache(target) ? callback : cb); + console.error('Unreachable: list should not be called on ' + + 'a node which has never fetched subnodes: ' + this.getPath()); + throw new Error('Unreachable: list should not be called on ' + + 'a node which has never fetched subnodes: ' + this.getPath()); } }, - extractZip : function (source, target, cb) { - var sourceNode, targetNode; - function checkValidExtractZip() { - var ret = checkTargetPath(source, true); - if (isError(ret)) { - return ret; - } - sourceNode = ret; + show : function (level) { // for debugging + var arr = []; + for (var i = 0; i < level; i++) { + arr.push('| '); + } + arr.push(this.name + '/'); + console.log(arr.join('')); - ret = checkTargetPath(target, false, true); - if (isError(ret)) { - return ret; - } - targetNode = ret; + this.dirs.forEach(function (subdir) { + subdir.show(level + 1); + }); - return null; - } + this.files.forEach(function (subdir) { + subdir.show(level + 1); + }); + }, - function callback(err) { - console.assert(sourceNode === root.getByAbsPath(source), - 'A source node has been modified duirng a ' + - 'extractZip request to server: ' + source); - console.assert(targetNode === root.getByAbsPath(target), - 'A target node has been modified duirng a ' + - 'extractZip request to server: ' + target); - if (err) { - cb(err); - } else { - root.putByAbsPath(target + '/', 'zip-extracted'); - cb(null); - } + getSummary : function () { + var subSummaries; + if (this.listed || !withinCache(this.getPath())) { + subSummaries = []; + this.dirs.forEach(function (dir) { + var val = dir.getSummary(); + console.assert(typeof val === 'object', + 'Summary of a subdir must be an object'); + subSummaries.push(val); + }); + this.files.forEach(function (file) { + var val = file.getSummary(); + console.assert(typeof val === 'string', + 'Summary of a file must be a string'); + subSummaries.push(val); + }); + } else { + subSummaries = null; } - target = pathUtil.detachSlash(target); - var err = checkValidExtractZip(); - if (err) { - setTimeout(cb.bind(null, err), 0); + if (this.name) { + return { n: this.name, s: subSummaries }; } else { - mount.extractZip(source, target, withinCache(target) ? callback : cb); + // only root can reach here + return subSummaries; } }, - _writeFile: function (target, data, cb) { - var file; - function checkValidWriteFile() { - var ret = checkTargetPath(target, undefined); - if (isError(ret)) { - return ret; - } - file = ret; + restoreFromSummary : function (subSummaries) { + if (subSummaries) { + console.assert(subSummaries instanceof Array, + 'SubSummaries must be an array'); - if (file && !(file instanceof File)) { - return 'The path "' + target + '" is not a path of a file'; - } + var self = this; + subSummaries.forEach(function (summary) { + var type = typeof summary; + if (type === 'object') { + var added = self.addSubnode(summary.n, true, 'restored'); + added.restoreFromSummary(summary.s); + } else if (type === 'string') { + self.addSubnode(summary, false, 'restored'); + } else { + console.assert(false, + 'Summary must be an object or string'); + } + }); + this.fetchedSubnodes = true; + } + } + }); - return null; + var FSCacheInner = declare(null, { + + constructor : function () { + // dirsToCaches_ must be an array of valid absolute paths + if (!dirsToCacheArg.every(function (dir) { + return isValidAbsPath(dir) && pathUtil.isDirPath(dir); + })) { + + throw new Error('The second argument must be an ' + + 'array of absolute directory paths'); } - function callback(err) { - console.assert(file === root.getByAbsPath(target), - 'A target node has been modified duirng a ' + - 'writeFile request to server: ' + target); - if (err) { - cb(err); - } else { - if (!file) { - var added = root.putByAbsPath(target, 'file-written'); - file = added[added.length - 1]; - } - if (typeof data === 'string') { - file.setContent(data, 'written'); - } else { - file.setContent(undefined, 'written'); + // they must represent disjoin set of directories. + var len = dirsToCacheArg.length; + for (var i = 0; i < len - 1; i++) { + var di = dirsToCacheArg[i]; + for (var j = i + 1; j < len; j++) { + if (di.indexOf(dirsToCacheArg[j]) === 0) { + throw new Error('Directories to cache are not disjoint'); } - cb(null); } } - var err = checkValidWriteFile(); - if (err) { - setTimeout(cb.bind(null, err), 0); - } else { - mount.writeFile(target, data, withinCache(target) ? callback : cb); - } - }, + // Now, it's ok to build an of object of FSCacheInner - // writeFile - writeFile : function (target, data, cb) { - //console.log('writeFile('+target+', data, cb)'); - var that = this; - var path, dir; - path = target.split(/[\\/]/); - path.pop(); - dir = path.join('/'); - this.isDirectory(dir, function (error, isDir) { - if (isDir === true) { - that._writeFile(target, data, cb); + // set private fields + fsURL = fsURLArg; + fsURLParsed = parseWFSURL(fsURL); + dirsToCache = dirsToCacheArg; + publishing = false; + mount = webida.fs.mountByFSID(fsURLParsed.fsid); + + root = new Directory(null, ''); // no parent, empty name + root.getByAbsPath = function (absPath) { + //console.log('hina temp: absPath = ' + absPath); + if (absPath.indexOf('/') === 0) { + return absPath === '/' ? this : this.getByRelPath(absPath.substr(1)); } else { - //console.log('If dir not exists, create dir then write file'); - that.createDirectory(dir, true, function (err) { - if (err) { - cb(err); - } else { - that._writeFile(target, data, cb); - } - }); + console.error('Unreachable: cannot get "' + absPath + '" under "/"'); + throw new Error('Unreachable: cannot get "' + absPath + '" under "/"'); } - }); + }; + root.putByAbsPath = function (absPath, caseStr) { + if (absPath.indexOf('/') === 0 && absPath.length > 1) { + return this.putByRelPath(absPath.substr(1), caseStr); + } else { + console.error('Unreachable: cannot put "' + absPath + '" under "/"'); + throw new Error('Unreachable: cannot put "' + absPath + '" under "/"'); + } + }; + //root.show(0); }, - // setMeta - setMeta : function (target, key, val, cb) { - var targetNode; - function checkValidSetMeta() { - var ret = checkTargetPath(target, true, true); - if (isError(ret)) { - return ret; - } - targetNode = ret; + getSummary : function () { + return root.getSummary(); + }, - if (!key || typeof key !== 'string') { - return 'Key must be a non-empty string'; - } + start : function (summary) { + if (summary) { + root.restoreFromSummary(summary); + root.fetchedSubnodes = false; // TODO: generalize this. + } - if (typeof val !== 'string') { - return 'Value must be a string'; + // add nodes for cache roots + dirsToCache.forEach(function (dir) { + root.putByAbsPath(dir, 'cache-root'); + }); + + //console.log('Starting FS-Cache.'); + //root.show(0); + publishing = true; + + subscribeToNotificationsOnFS(this); + }, + + invalidateFileContents: function (path) { + if (isValidAbsPath(path) && withinCache(path)) { + var node = root.getByAbsPath(path); + if (!node) { + throw new Error('Not in the cache: ' + path); } - return null; + node.invalidateFileContents(); + + } else { + throw new Error('The path "' + path + '" is not a valid path being cached'); } + }, - function callback(err) { - console.assert(targetNode === root.getByAbsPath(target), - 'Target node has been modified during a request of ' + - 'setMeta to server:' + target); - if (err) { - cb(err); - } else { - if (targetNode) { - targetNode.setMetadata(key, val, 'written'); - cb(null); - } else { - mount.isDirectory(target, function (err, isDir) { - if (err) { - console.error('Error in isDirectory call while setting metadata: ' + err); - } else { - var added = root.putByAbsPath( - (isDir ? pathUtil.attachSlash : pathUtil.detachSlash)(target), 'inferred'); - targetNode = added[added.length - 1]; - targetNode.setMetadata(key, val, 'written'); - } - cb(null); - }); - } + invalidateMetadata: function (path, key) { + if (isValidAbsPath(path) && withinCache(path)) { + var node = root.getByAbsPath(path); + if (!node) { + throw new Error('Not in the cache: ' + path); } - } - var err = checkValidSetMeta(); - if (err) { - setTimeout(cb.bind(null, err), 0); - } else { - mount.setMeta(target, key, val, callback); - } - }, + node.invalidateMetadata(key); - lockFile: function (path, cb) { - if (isValidAbsPath(path)) { - mount.lockFile.apply(mount, arguments); } else { - setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); + throw new Error('The path "' + path + '" is not a valid path being cached'); } }, - unlockFile: function (path, cb) { - if (isValidAbsPath(path)) { - mount.unlockFile.apply(mount, arguments); - } else { - setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); - } - }, + refreshHierarchy: function (path, options, cb) { + if (isValidAbsPath(path) && withinCache(path)) { + var node = root.getByAbsPath(path); + if (!node) { + throw new Error('Not in the cache: ' + path); + } - //--------------------------- - // FS API wrapper - methods WITHOUT update - //--------------------------- + var level; + if (node.getType() === TYPE_FILE) { + node = node.parent; + level = 1; + } else { + if (options && 'level' in options) { + level = Math.floor(options.level); + } else { + throw new Error('Level must be given in the options argument'); + } + } + + cb = cb || doNothing; + node.refreshHierarchy(level, cb); - getLockedFiles: function (path, cb) { - if (isValidAbsPath(path)) { - mount.getLockedFiles.apply(mount, arguments); } else { - setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); + throw new Error('The path "' + path + '" is not a valid path being cached'); } }, - addAlias: function (path, expireTime, cb) { - if (isValidAbsPath(path)) { - mount.addAlias.apply(mount, arguments); + refreshFileContents: function (path, options) { + if (isValidAbsPath(path) && withinCache(path)) { + var node = root.getByAbsPath(path); + if (!node) { + throw new Error('Not in the cache: ' + path); + } + + if (node.getType() === TYPE_FILE) { // only for files + node.refreshFileContents(); + } else { + var level; + if (options && 'level' in options) { + level = Math.floor(options.level); + } else { + throw new Error('Level must be given in the \'options\' argument'); + } + node.refreshFileContents(level); // + } } else { - setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); + throw new Error('The path "' + path + '" is not a valid path being cached'); } }, - // deleteAlias - deleteAlias : function () { - mount.deleteAlias.apply(mount, arguments); - }, + refreshMetadata: function (path, key, options) { + if (isValidAbsPath(path) && withinCache(path)) { + var node = root.getByAbsPath(path); + if (!node) { + throw new Error('Not in the cache: ' + path); + } - // exec - exec : function (path, info, cb) { - if (isValidAbsPath(path)) { - mount.exec.apply(mount, arguments); + if (node.getType() === TYPE_FILE) { // only for files + node.refreshMetadata(key); + } else { + var level; + if (options && 'level' in options) { + level = Math.floor(options.level); + } else { + throw new Error('Level must be given in the \'options\' argument'); + } + node.refreshMetadata(level, key); // + } } else { - setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); + throw new Error('The path "' + path + '" is not a valid path being cached'); } }, - // exportZip - exportZip : function (sourceArr/*, fileName*/) { - if (sourceArr.every(function (src) { return isValidAbsPath(src); })) { - mount.exportZip.apply(mount, arguments); + // convenience API: refresh all (hierarchy, file contents, and metadata) + refresh : function (path, options, cbWhenHierarchyDone) { + var opt = { level: 1 }; + if (options) { + _.extend(opt, options); } + + var self = this; + this.refreshHierarchy(path, opt, function () { + if (cbWhenHierarchyDone) { + cbWhenHierarchyDone(); + } + + self.refreshFileContents(path, opt); + self.refreshMetadata(path, undefined/* every key */, opt); + }); }, - // exists - exists : function (target, cb) { - var ret = checkTargetPath(target); - if (isError(ret)) { - setTimeout(cb.bind(null, ret), 0); - } else { - var targetNode = ret; - if (targetNode) { - setTimeout(cb.bind(null, null, true), 0); - } else if (targetNode === null) { - setTimeout(cb.bind(null, null, false), 0); - } else { - //console.log('hina temp: exists goes to server'); - mount.exists(target, cb); + queryPaths : function (dirPath, condition) { + if (isValidAbsPath(dirPath) && pathUtil.isDirPath(dirPath) && withinCache(dirPath)) { + var dirNode = root.getByAbsPath(dirPath); + if (!dirNode) { + return null; } + + var nodeArr = []; + dirNode.collectNodes(nodeArr, condition); + return nodeArr.map(function (node) { + return node.getPath(); + }); + + } else { + throw new Error('The path "' + dirPath + + '" does not represent a directory being cached'); } }, - getAliasInfo : function () { - mount.getAliasInfo.apply(mount, arguments); - }, + //--------------------------- + // FS API wrapper - methods WITH update + //--------------------------- - // getMeta - getMeta : function (target, key, cb) { - var targetNode; + // copy + copy : function (src, dst, recursive, cb) { - function checkValidGetMeta() { - var ret = checkTargetPath(target, true, true); + var srcNode, dstNode; + function checkValidCopy() { + var ret = checkTargetPath(src, true, true); if (isError(ret)) { return ret; } - targetNode = ret; + srcNode = ret; + + ret = checkTargetPath(dst, null); + if (isError(ret)) { + return ret; + } + dstNode = ret; + + if (src === dst) { + return 'Source and destination paths are identical: ' + src + '.'; + } + + if (dst.indexOf(src + '/') === 0) { + return 'Cannot copy a directory into its descendant'; + } + + if (srcNode instanceof Directory && !recursive) { + return 'Copying a directory with recursive option unset is disallowed.'; + } + + var divided = pathUtil.dividePath(dst); + if (root.getByAbsPath(divided[0]) === null) { + return 'Destination directory "' + divided[0] + '" does not exist.'; + } return null; } - function callback(err, val) { - console.assert(targetNode === root.getByAbsPath(target), - 'Target node has been modified during a ' + - 'getMeta request to server : ' + target); + function callback(err) { + + function handleCopySuccess(isDir) { + console.assert(srcNode === root.getByAbsPath(src), + 'A source node has been modified during a ' + + 'copy request to server: ' + src); + console.assert(dstNode === root.getByAbsPath(dst), + 'A destination node has been modified during a ' + + 'copy request to server: ' + dst); + + var t = dst + (isDir ? '/' : ''); + var addedNodes = root.putByAbsPath(t, 'copied'); + if (!addedNodes) { // because the target has been already present. + fsCache.refresh(t, { level: -1 }); + } + cb(null); + } + if (err) { + /* TODO: continue 'serverless operations' + if (err === 'server unreachable' && srcNode && !dstNode) { + } else { + cb(err); + return; + } + */ cb(err); } else { - if (targetNode) { - targetNode.setMetadata(key, val, 'fetched'); - cb(null, val); + if (srcNode) { + handleCopySuccess(srcNode instanceof Directory); } else { - mount.isDirectory(target, function (err, isDir) { + mount.isDirectory(dst, function (err, isDir) { if (err) { - console.error('Error in isDirectory call while getting metadata: ' + err); + cb(err); } else { - var added = root.putByAbsPath( - (isDir ? pathUtil.attachSlash : pathUtil.detachSlash)(target), 'inferred'); - targetNode = added[added.length - 1]; - targetNode.setMetadata(key, val, 'fetched'); + handleCopySuccess(isDir); } - cb(null, val); }); - } } } - var ret = checkValidGetMeta(); //checkTargetPath(target, true, true); - if (isError(ret)) { - setTimeout(cb.bind(null, ret), 0); + if (typeof recursive === 'function') { + cb = recursive; + recursive = false; // false is default + } + + src = pathUtil.detachSlash(src); + var err = checkValidCopy(); + if (err) { + setTimeout(cb.bind(null, err), 0); } else { - var val; - if (targetNode === undefined || - (val = targetNode.metadata[key]) === undefined || - targetNode.metadataInvalidated[key]) { - mount.getMeta(target, key, callback); - } else { - setTimeout(cb.bind(null, null, val), 0); - } + mount.copy(src, dst, recursive, withinCache(dst) ? callback : cb); } }, - // isDirectory - isDirectory : function (target, cb) { - var targetNode; - function callback(err, isDir) { - console.assert(targetNode === root.getByAbsPath(target), - 'Target node has been modified during a ' + - 'isDirectory request to server : ' + target); + // move + move : function (src, dst, cb) { + + var srcNode, dstNode; + function checkValidMove() { + var ret = checkTargetPath(src, true, true); + if (isError(ret)) { + return ret; + } + srcNode = ret; + + ret = checkTargetPath(dst, null); + if (isError(ret)) { + return ret; + } + dstNode = ret; + + if (src === dst) { + return 'Source and destination paths are identical: ' + src + '.'; + } + + if (dst.indexOf(src + '/') === 0) { + return 'Cannot move a directory into its descendant'; + } + + var divided = pathUtil.dividePath(dst); + if (root.getByAbsPath(divided[0]) === null) { + return 'Destination directory "' + divided[0] + '" does not exist.'; + } + + return null; + } + + function callback(err) { + function handleMoveSuccess(isDir) { + console.assert(srcNode === root.getByAbsPath(src), + 'A source node has been modified ' + + 'duirng a move request to server: ' + src); + console.assert(dstNode === root.getByAbsPath(dst), + 'A destination node has been modified ' + + 'duirng a move request to server: ' + dst); + + if (withinCache(dst)) { + var t = dst + (isDir ? '/' : ''); + var addedNodes = root.putByAbsPath(t, 'moved'); + if (!addedNodes) { // because the target has been already present. + fsCache.refresh(t, { level: -1 }); + } + } + if (srcNode) { + srcNode.detach(withinCache(dst) ? dst : undefined); + } + cb(null); + } + if (err) { cb(err); } else { - if (withinCache(target)) { - console.assert(targetNode === undefined); - var added = root.putByAbsPath(target + (isDir ? '/' : ''), 'inferred'); - targetNode = added && added[added.length - 1]; - console.assert(targetNode); + if (srcNode) { + handleMoveSuccess(srcNode instanceof Directory); + } else { + mount.isDirectory(dst, function (err, isDir) { + if (err) { + cb(err); + } else { + handleMoveSuccess(isDir); + } + }); } - cb(null, isDir); } } - target = pathUtil.detachSlash(target); - var ret = checkTargetPath(target, true, true); - if (isError(ret)) { - setTimeout(cb.bind(null, ret), 0); + src = pathUtil.detachSlash(src); + var err = checkValidMove(); + if (err) { + setTimeout(cb.bind(null, err), 0); } else { - targetNode = ret; - if (targetNode === undefined) { - mount.isDirectory(target, callback); - } else { - console.assert(targetNode); - setTimeout(cb.bind(null, null, targetNode instanceof Directory), 0); - } - + mount.move(src, dst, (srcNode || withinCache(dst)) ? callback : cb); } }, - // isEmpty - isEmpty : function (target, cb) { + // createDirectory + createDirectory : function (target, recursive, cb) { var targetNode; - function checkValidIsEmpty() { - var ret = checkTargetPath(target, true, true); + function checkValidCreateDirectory() { + var ret = checkTargetPath(target, false, true); if (isError(ret)) { return ret; } targetNode = ret; - if (targetNode && !(targetNode instanceof Directory)) { - return 'The path "' + target + '" does not represent a directory'; + if (!recursive) { + var parentPath = pathUtil.dividePath(target)[0]; + var parentNode = root.getByAbsPath(parentPath); + if (parentNode === null) { + return 'Parent directory "' + parentPath + + '" does not exist and the recursive option is not set.'; + } } return null; } - function callback(err, isEmpty) { + function callback(err) { console.assert(targetNode === root.getByAbsPath(target), - 'Target node has been modified during a ' + - 'isEmpty request to server : ' + target); + 'A target node has been modified during a ' + + 'createDirectory request to server: ' + target); if (err) { cb(err); } else { - if (withinCache(target)) { - console.assert(targetNode === undefined); - var added = root.putByAbsPath(target + '/', 'inferred'); - targetNode = added && added[added.length - 1]; - console.assert(targetNode); - } - cb(null, isEmpty); + root.putByAbsPath(target + '/', 'dir-created'); + cb(null); } } + if (typeof recursive === 'function') { + cb = recursive; + recursive = false; // false is default + } + target = pathUtil.detachSlash(target); - var err = checkValidIsEmpty(); + var err = checkValidCreateDirectory(); if (err) { setTimeout(cb.bind(null, err), 0); } else { - if (targetNode === undefined) { - mount.isEmpty(target, callback); + mount.createDirectory(target, recursive, withinCache(target) ? callback : cb); + } + }, + + createZip : function (sources, target, cb) { + var targetNode; + function checkValidCreateZip() { + if (sources.some(function (path) { return !isValidAbsPath(path); })) { + return 'List of sources contains a path which is not a valid absolute path.'; + } + + var ret = checkTargetPath(target, false); + if (isError(ret)) { + return ret; + } + targetNode = ret; + + return null; + } + + function callback(err) { + console.assert(targetNode === root.getByAbsPath(target), + 'A target node has been modified during a ' + + 'createZip request to server: ' + target); + if (err) { + cb(err); } else { - console.assert(targetNode); - var isEmptyLocal = targetNode.isEmpty(); - if (!isEmptyLocal || targetNode.fetchedSubnodes) { - setTimeout(cb.bind(null, null, isEmptyLocal), 0); - } else { - mount.isEmpty(target, cb); - } + root.putByAbsPath(target, 'zip-created'); + cb(null); } + } + var err = checkValidCreateZip(); + if (err) { + setTimeout(cb.bind(null, err), 0); + } else { + mount.createZip(sources, target, withinCache(target) ? callback : cb); } }, - // isFile - isFile : function (target, cb) { + // delete + 'delete' : function (target, recursive, cb) { var targetNode; - function callback(err, isFile) { + function callback(err) { console.assert(targetNode === root.getByAbsPath(target), - 'Target node has been modified during a ' + - 'isFile request to server : ' + target); + 'A target node has been modified duirng a ' + + 'delete request to server: ' + target); if (err) { cb(err); } else { - if (withinCache(target)) { - console.assert(targetNode === undefined); - var added = root.putByAbsPath(target + (isFile ? '' : '/'), 'inferred'); - targetNode = added && added[added.length - 1]; - console.assert(targetNode); + if (targetNode) { + targetNode.detach(); } - cb(null, isFile); + cb(null); } } - var ret = checkTargetPath(target, true); + if (typeof recursive === 'function') { + cb = recursive; + recursive = false; // false is default + } + + var ret = checkTargetPath(target, true, true); if (isError(ret)) { setTimeout(cb.bind(null, ret), 0); } else { targetNode = ret; - if (targetNode === undefined) { - mount.isFile(target, callback); - } else { - console.assert(targetNode); - setTimeout(cb.bind(null, null, targetNode instanceof File), 0); - } - + mount.delete(target, recursive, withinCache(target) ? callback : cb); } }, - // list - /* - list : function (target, recursive, cb) { - if (typeof recursive === 'function') { - cb = recursive; - recursive = false; - } - - this.listEx(target, { recursive: recursive }, cb); - }, - */ + extractZip : function (source, target, cb) { + var sourceNode, targetNode; + function checkValidExtractZip() { + var ret = checkTargetPath(source, true); + if (isError(ret)) { + return ret; + } + sourceNode = ret; - list : function (target, recursive, cb) { - var node; - function checkValidList() { - var ret = checkTargetPath(target, true, true); + ret = checkTargetPath(target, false, true); if (isError(ret)) { return ret; } - node = ret; - - if (node && !(node instanceof Directory)) { - return 'The path "' + target + '" does not represent a directory.'; - } + targetNode = ret; return null; } - function callback0(err, subnodes) { - console.assert(node === root.getByAbsPath(target), - 'Target node has been modified during a ' + - 'list request to server : ' + target); - if (err) { - cb(err); - } else { - node.updateSubnodes(subnodes.filter(isDir), true, 'fetched'); - node.updateSubnodes(subnodes.filter(isFile), false, 'fetched'); - node.fetchedSubnodes = true; - cb(null, subnodes); - node.listed = true; - } - } - - function callback1(err, subnodes) { - console.assert(node === root.getByAbsPath(target), - 'Target node has been modified during a ' + - 'list request to server : ' + target); + function callback(err) { + console.assert(sourceNode === root.getByAbsPath(source), + 'A source node has been modified duirng a ' + + 'extractZip request to server: ' + source); + console.assert(targetNode === root.getByAbsPath(target), + 'A target node has been modified duirng a ' + + 'extractZip request to server: ' + target); if (err) { cb(err); } else { - var added = root.putByAbsPath(target, 'inferred'); - node = added && added[added.length - 1]; - node.updateSubnodes(subnodes.filter(isDir), true, 'fetched'); - node.updateSubnodes(subnodes.filter(isFile), false, 'fetched'); - node.fetchedSubnodes = true; - cb(null, subnodes); - node.listed = true; + root.putByAbsPath(target + '/', 'zip-extracted'); + cb(null); } } - if (typeof recursive === 'function') { - cb = recursive; - recursive = false; - } - - target = pathUtil.attachSlash(target); - var err = checkValidList(); + target = pathUtil.detachSlash(target); + var err = checkValidExtractZip(); if (err) { setTimeout(cb.bind(null, err), 0); } else { - if (withinCache(target)) { - if (node) { - if (node.fetchedSubnodes && !recursive) { - //console.log('hina temp: list from fs-cache: ' + target); - setTimeout(cb.bind(null, null, node.list()), 0); - node.listed = true; - } else { - //console.log('hina temp: list from server (case 0): ' + target); - mount.list(target, recursive, callback0); // case 0 - } - } else if (node === undefined) { - //console.log('hina temp: list from server (case 1): ' + target); - mount.list(target, recursive, callback1); // case 1 - } else { - console.assert(false, 'Unreachable'); - } - } else { - //console.log('hina temp: list from server (case 2): ' + target); - mount.list(target, recursive, cb); - } + mount.extractZip(source, target, withinCache(target) ? callback : cb); } }, - - // listEx - listEx : function (target, options, cb) { - var node; - function checkValidListEx() { - var ret = checkTargetPath(target, true, true); + _writeFile: function (target, data, cb) { + var file; + function checkValidWriteFile() { + var ret = checkTargetPath(target, undefined); if (isError(ret)) { return ret; } - node = ret; - - if (node && !(node instanceof Directory)) { - return 'The path "' + target + '" does not represent a directory.'; - } + file = ret; - if (options.dirOnly && options.fileOnly) { - return 'Cannot simultaneously be dirOnly and fileOnly'; + if (file && !(file instanceof File)) { + return 'The path "' + target + '" is not a path of a file'; } return null; } - function callback(err, subnodes) { - console.assert(node === root.getByAbsPath(target), - 'Target node has been modified during a ' + - 'list request to server : ' + target); + function callback(err) { + console.assert(file === root.getByAbsPath(target), + 'A target node has been modified duirng a ' + + 'writeFile request to server: ' + target); if (err) { cb(err); } else { - if (!node) { - var added = root.putByAbsPath(target, 'inferred'); - node = added && added[added.length - 1]; + if (!file) { + var added = root.putByAbsPath(target, 'file-written'); + file = added[added.length - 1]; } - node.updateSubnodes(subnodes.filter(isDir), true, 'fetched'); - node.updateSubnodes(subnodes.filter(isFile), false, 'fetched'); - node.fetchedSubnodes = true; - - if (options.dirOnly) { - subnodes = subnodes.filter(isDir); - } else if (options.fileOnly) { - subnodes = subnodes.filter(isFile); + if (typeof data === 'string') { + file.setContent(data, 'written'); + } else { + file.setContent(undefined, 'written'); } - cb(null, subnodes); - node.listed = true; + cb(null); } } - if (typeof options === 'function') { - cb = options; - options = {}; - } - - target = pathUtil.attachSlash(target); - var err = checkValidListEx(); + var err = checkValidWriteFile(); if (err) { setTimeout(cb.bind(null, err), 0); } else { - if (withinCache(target)) { - if (node && node.fetchedSubnodes && !options.recursive) { - //console.log('hina temp: list from fs-cache: ' + target); - var subnodes = node.list(); - if (options.dirOnly) { - subnodes = subnodes.filter(isDir); - } else if (options.fileOnly) { - subnodes = subnodes.filter(isFile); - } - setTimeout(cb.bind(null, null, subnodes), 0); - node.listed = true; - } else { - //console.log('hina temp: list from server (inside cache): ' + target); - // ignore dirOnly and fileOnly options. - mount.listEx(target, { recursive: options.recursive }, callback); // case 0 - } + mount.writeFile(target, data, withinCache(target) ? callback : cb); + } + }, + + // writeFile + writeFile : function (target, data, cb) { + //console.log('writeFile('+target+', data, cb)'); + var that = this; + var path, dir; + path = target.split(/[\\/]/); + path.pop(); + dir = path.join('/'); + this.isDirectory(dir, function (error, isDir) { + if (isDir === true) { + that._writeFile(target, data, cb); } else { - //console.log('hina temp: list from server (outside cache): ' + target); - mount.listEx(target, options, cb); + //console.log('If dir not exists, create dir then write file'); + that.createDirectory(dir, true, function (err) { + if (err) { + cb(err); + } else { + that._writeFile(target, data, cb); + } + }); } - } + }); }, - // readFile - readFile : function (target, responseType, cb) { + // setMeta + setMeta : function (target, key, val, cb) { var targetNode; - if (!cb) { - cb = responseType; - responseType = ''; - } - function checkValidReadFile() { - var ret = checkTargetPath(target, true); + function checkValidSetMeta() { + var ret = checkTargetPath(target, true, true); if (isError(ret)) { return ret; } targetNode = ret; - if (targetNode && !(targetNode instanceof File)) { - return 'The path "' + target + '" does not represent a file'; + if (!key || typeof key !== 'string') { + return 'Key must be a non-empty string'; + } + + if (typeof val !== 'string') { + return 'Value must be a string'; } return null; } - function callback(err, content) { - var targetNodeAfter = root.getByAbsPath(target); - console.assert(targetNode === targetNodeAfter, - 'Target node has been modified during a ' + - 'readFile request to server : ' + target + - ', before(' + targetNode + - '), after(' + targetNodeAfter + ')'); + function callback(err) { + console.assert(targetNode === root.getByAbsPath(target), + 'Target node has been modified during a request of ' + + 'setMeta to server:' + target); if (err) { cb(err); } else { - if (targetNode === undefined) { - var added = root.putByAbsPath(target, 'inferred'); - targetNode = (added && added[added.length - 1]) || targetNodeAfter; + if (targetNode) { + targetNode.setMetadata(key, val, 'written'); + cb(null); + } else { + mount.isDirectory(target, function (err, isDir) { + if (err) { + console.error('Error in isDirectory call while setting metadata: ' + err); + } else { + var added = root.putByAbsPath( + (isDir ? pathUtil.attachSlash : pathUtil.detachSlash)(target), 'inferred'); + targetNode = added[added.length - 1]; + targetNode.setMetadata(key, val, 'written'); + } + cb(null); + }); } - console.assert(targetNode); - - targetNode.setContent(content, 'fetched'); - cb(null, content); } } - var err = checkValidReadFile(); + var err = checkValidSetMeta(); if (err) { setTimeout(cb.bind(null, err), 0); } else { - // FIXME: cache and compare responseType too - if (withinCache(target)) { - var content; - if (targetNode === undefined || - (content = targetNode.content) === undefined || - targetNode.contentInvalidated) { - mount.readFile(target, responseType, callback); - } else { - console.assert(content !== null); - setTimeout(cb.bind(null, null, content), 0); - } - } else { - mount.readFile(target, responseType, cb); - } + mount.setMeta(target, key, val, callback); } }, - // searchFiles - searchFiles : function (keyword, where, options, cb) { - if (isValidAbsPath(where)) { - mount.searchFiles.apply(mount, arguments); + lockFile: function (path, cb) { + if (isValidAbsPath(path)) { + mount.lockFile.apply(mount, arguments); } else { - setTimeout(cb.bind(null, 'The path "' + where + '" is not a valid absolute path'), 0); + setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); } }, - // replaceFiles - replaceFiles : function (keyword, replace, where, options, cb) { - if (_.every(where, function (path) { return isValidAbsPath(path); })) { - mount.replaceFiles.apply(mount, arguments); + unlockFile: function (path, cb) { + if (isValidAbsPath(path)) { + mount.unlockFile.apply(mount, arguments); } else { - setTimeout(cb.bind(null, 'The list of where contains a string which ' + - 'is not a valid absolute path.'), 0); + setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); } }, - // stat - stat : function (pathList, cb) { - if (pathList.every(function (path) { return isValidAbsPath(path); })) { - mount.stat.apply(mount, arguments); + //--------------------------- + // FS API wrapper - methods WITHOUT update + //--------------------------- + + getLockedFiles: function (path, cb) { + if (isValidAbsPath(path)) { + mount.getLockedFiles.apply(mount, arguments); } else { - setTimeout(cb.bind(null, 'The list of paths contains a string which ' + - 'is not a valid absolute path.'), 0); + setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); } - } - }); - - //--------------------------- - //--------------------------- - // private methods of FSCacheInner - //--------------------------- - //--------------------------- + }, - //--------------------------- - // utility functions - //--------------------------- + addAlias: function (path, expireTime, cb) { + if (isValidAbsPath(path)) { + mount.addAlias.apply(mount, arguments); + } else { + setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); + } + }, - function parseWFSURL(fsURL) { - var uri = new URI(fsURL); - var ret = { - fsServer: uri.host(), - fsid: uri.segment(0) - }; - uri.segment(0, ''); // drop the fsid - ret.path = uri.path(true); + // deleteAlias + deleteAlias : function () { + mount.deleteAlias.apply(mount, arguments); + }, - return ret; - } - function subscribeToNotificationsOnFS(fsCache) { - function isIgnoredNotification(data, checkedURLs) { - // ignore changed caused by myself - var mySessionID = webida.auth.getSessionID(); - if (!data.sid) { - console.error('The session id of a notification is unknown'); - return true; + // exec + exec : function (path, info, cb) { + if (isValidAbsPath(path)) { + mount.exec.apply(mount, arguments); + } else { + setTimeout(cb.bind(null, 'The path "' + path + '" is not a valid absolute path'), 0); } + }, - if (mySessionID === data.sid) { - console.log('notification ignored: changes by the current app'); - return true; + // exportZip + exportZip : function (sourceArr/*, fileName*/) { + if (sourceArr.every(function (src) { return isValidAbsPath(src); })) { + mount.exportZip.apply(mount, arguments); } + }, - if (checkedURLs.every(function (url) { return !withinCache(data[url]); })) { - console.log('notification ignored: changes outside cache'); - return true; + // exists + exists : function (target, cb) { + var ret = checkTargetPath(target); + if (isError(ret)) { + setTimeout(cb.bind(null, ret), 0); + } else { + var targetNode = ret; + if (targetNode) { + setTimeout(cb.bind(null, null, true), 0); + } else if (targetNode === null) { + setTimeout(cb.bind(null, null, false), 0); + } else { + //console.log('hina temp: exists goes to server'); + mount.exists(target, cb); + } } + }, - return false; - } + getAliasInfo : function () { + mount.getAliasInfo.apply(mount, arguments); + }, - topic.subscribe('sys.fs.file.written', function (data) { - if (isIgnoredNotification(data, ['url'])) { - return; - } + // getMeta + getMeta : function (target, key, cb) { + var targetNode; - var urlParsed = parseWFSURL(data.url); - var existing = root.getByAbsPath(urlParsed.path); - if (!existing) { - // file created - if (existing === null) { - root.putByAbsPath(urlParsed.path, 'file-written'); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.file.written] for "' + - urlParsed.path + '" (as a file creation)'); - topic.publish('#REQUEST.log', ''); + function checkValidGetMeta() { + var ret = checkTargetPath(target, true, true); + if (isError(ret)) { + return ret; } - } else if (existing.getType() === TYPE_FILE) { - // file written - existing.invalidateFileContents(); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.file.written] for "' + - urlParsed.path + '" (as a file cache invalidation)'); - topic.publish('#REQUEST.log', ''); - } else { - console.error('sys.fs.file.written arrived for a non-file "' + - urlParsed.path + '"'); - } + targetNode = ret; - }); - topic.subscribe('sys.fs.file.deleted', function (data) { - if (isIgnoredNotification(data, ['url'])) { - return; + return null; } - var urlParsed = parseWFSURL(data.url); - var existing = root.getByAbsPath(urlParsed.path); - if (!existing) { - if (existing === null) { - console.error('sys.fs.file.deleted arrived for an absent file "' + - urlParsed.path + '"'); - } - } else if (existing.getType() === TYPE_FILE) { - existing.detach(); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.file.deleted] for "' + - urlParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - } else { - console.error('sys.fs.file.deleted arrived for a non-file "' + - urlParsed.path + '"'); - } + function callback(err, val) { + console.assert(targetNode === root.getByAbsPath(target), + 'Target node has been modified during a ' + + 'getMeta request to server : ' + target); + if (err) { + cb(err); + } else { + if (targetNode) { + targetNode.setMetadata(key, val, 'fetched'); + cb(null, val); + } else { + mount.isDirectory(target, function (err, isDir) { + if (err) { + console.error('Error in isDirectory call while getting metadata: ' + err); + } else { + var added = root.putByAbsPath( + (isDir ? pathUtil.attachSlash : pathUtil.detachSlash)(target), 'inferred'); + targetNode = added[added.length - 1]; + targetNode.setMetadata(key, val, 'fetched'); + } + cb(null, val); + }); - }); - topic.subscribe('sys.fs.dir.created', function (data) { - if (isIgnoredNotification(data, ['url'])) { - return; + } + } } - var urlParsed = parseWFSURL(data.url); - var existing = root.getByAbsPath(urlParsed.path); - if (!existing) { - if (existing === null) { - root.putByAbsPath(pathUtil.attachSlash(urlParsed.path), 'dir-created'); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.dir.created] for "' + - urlParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - } - } else if (existing.getType() === TYPE_DIRECTORY) { - console.error('sys.fs.dir.created arrived for an existing directory "' + - urlParsed.path + '"'); + var ret = checkValidGetMeta(); //checkTargetPath(target, true, true); + if (isError(ret)) { + setTimeout(cb.bind(null, ret), 0); } else { - console.error('sys.fs.dir.created arrived for a non-directory "' + - urlParsed.path + '"'); - } - }); - topic.subscribe('sys.fs.dir.deleted', function (data) { - if (isIgnoredNotification(data, ['url'])) { - return; + var val; + if (targetNode === undefined || + (val = targetNode.metadata[key]) === undefined || + targetNode.metadataInvalidated[key]) { + mount.getMeta(target, key, callback); + } else { + setTimeout(cb.bind(null, null, val), 0); + } } + }, - var urlParsed = parseWFSURL(data.url); - var existing = root.getByAbsPath(urlParsed.path); - if (!existing) { - if (existing === null) { - console.error('sys.fs.dir.deleted arrived for an absent directory "' + - urlParsed.path + '"'); + // isDirectory + isDirectory : function (target, cb) { + var targetNode; + function callback(err, isDir) { + console.assert(targetNode === root.getByAbsPath(target), + 'Target node has been modified during a ' + + 'isDirectory request to server : ' + target); + if (err) { + cb(err); + } else { + if (withinCache(target)) { + console.assert(targetNode === undefined); + var added = root.putByAbsPath(target + (isDir ? '/' : ''), 'inferred'); + targetNode = added && added[added.length - 1]; + console.assert(targetNode); + } + cb(null, isDir); } - } else if (existing.getType() === TYPE_DIRECTORY) { - existing.detach(); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.dir.deleted] for "' + - urlParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - } else { - console.error('sys.fs.dir.deleted arrived for a non-directory "' + - urlParsed.path + '"'); - } - }); - topic.subscribe('sys.fs.node.intractableChanges', function (data) { - if (isIgnoredNotification(data, ['url'])) { - return; } - var urlParsed = parseWFSURL(data.url); - var existing = root.getByAbsPath(urlParsed.path); - if (!existing) { - if (existing === null) { - console.error('sys.fs.dir.intractableChanges arrived for an absent directory "' + - urlParsed.path + '"'); + target = pathUtil.detachSlash(target); + var ret = checkTargetPath(target, true, true); + if (isError(ret)) { + setTimeout(cb.bind(null, ret), 0); + } else { + targetNode = ret; + if (targetNode === undefined) { + mount.isDirectory(target, callback); + } else { + console.assert(targetNode); + setTimeout(cb.bind(null, null, targetNode instanceof Directory), 0); } - } else if (existing.getType() === TYPE_DIRECTORY) { - fsCache.refresh(urlParsed.path, { level: -1 }, function () { - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.node.intractableChanges] for "' + - urlParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - onNodeChanges(urlParsed.path); - }); - } else { - console.error('sys.fs.dir.intractable-changes arrived for a non-directory "' + - urlParsed.path + '"'); } + }, - }); - topic.subscribe('sys.fs.node.moved', function (data) { - if (isIgnoredNotification(data, ['srcURL', 'dstURL'])) { - return; + // isEmpty + isEmpty : function (target, cb) { + var targetNode; + function checkValidIsEmpty() { + var ret = checkTargetPath(target, true, true); + if (isError(ret)) { + return ret; + } + targetNode = ret; + + if (targetNode && !(targetNode instanceof Directory)) { + return 'The path "' + target + '" does not represent a directory'; + } + + return null; } - var dstURLParsed; - if (withinCache(data.dstURL)) { - dstURLParsed = parseWFSURL(data.dstURL); - mount.isDirectory(dstURLParsed.path, function (err, isDir) { - if (err) { - console.log('Cannot figure out whether the destination "' + dstURLParsed.path + - '" of a notification sys.fs.node.moved is a directory or not: ' + err); - console.log('The notification is ignored.'); - } else { - var existing = root.getByAbsPath(dstURLParsed.path); - if (existing) { - if ((existing.getType() === TYPE_DIRECTORY) === isDir) { - if (isDir) { - console.error('sys.fs.node.moved arraived for an existing "' + - dstURLParsed.path + '" as its destination'); - } else { - existing.invalidateFileContents(); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.node.moved] ' + - 'for its overwritten target file "' + dstURLParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - } - } else { - console.error('The type of the destination of a notification sys.fs.node.moved ' + - 'does not match that of the corresponding node in the fs-cache for "' + - dstURLParsed.path + '"'); - } - } else { - if (existing === null) { - root.putByAbsPath((isDir ? - pathUtil.attachSlash : - pathUtil.detachSlash)(dstURLParsed.path), - 'moved'); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.node.moved] for its target "' + - dstURLParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - } - } + function callback(err, isEmpty) { + console.assert(targetNode === root.getByAbsPath(target), + 'Target node has been modified during a ' + + 'isEmpty request to server : ' + target); + if (err) { + cb(err); + } else { + if (withinCache(target)) { + console.assert(targetNode === undefined); + var added = root.putByAbsPath(target + '/', 'inferred'); + targetNode = added && added[added.length - 1]; + console.assert(targetNode); } - }); + cb(null, isEmpty); + } } - if (withinCache(data.srcURL)) { - var srcURLParsed = parseWFSURL(data.srcURL); - var existing = root.getByAbsPath(srcURLParsed.path); - if (!existing) { - if (existing === null) { - console.error('sys.fs.node.moved arrived for an absent "' + - srcURLParsed.path + '" as its source'); - } + target = pathUtil.detachSlash(target); + var err = checkValidIsEmpty(); + if (err) { + setTimeout(cb.bind(null, err), 0); + } else { + if (targetNode === undefined) { + mount.isEmpty(target, callback); } else { - existing.detach(withinCache(data.dstURL) ? dstURLParsed.path : undefined); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.node.moved] for its source "' + - srcURLParsed.path + '"'); - topic.publish('#REQUEST.log', ''); + console.assert(targetNode); + var isEmptyLocal = targetNode.isEmpty(); + if (!isEmptyLocal || targetNode.fetchedSubnodes) { + setTimeout(cb.bind(null, null, isEmptyLocal), 0); + } else { + mount.isEmpty(target, cb); + } } - } - // TODO: need to publish fs.cache.node.moved? - // But the topic does not support the case when the source is out of the current file system. - }); - topic.subscribe('sys.fs.node.copied', function (data) { - if (isIgnoredNotification(data, ['dstURL'])) { - return; } + }, - var existing; - var dstURLParsed = parseWFSURL(data.dstURL); - if (withinCache(dstURLParsed.path)) { - mount.isDirectory(dstURLParsed.path, function (err, isDir) { - if (err) { - console.log('Cannot figure out whether the destination "' + dstURLParsed.path + - '" of a notification sys.fs.node.copied is a directory or not: ' + err); - console.log('The notification is ignored.'); - } else { - existing = root.getByAbsPath(dstURLParsed.path); - if (existing) { - if ((existing.getType() === TYPE_DIRECTORY) === isDir) { - if (isDir) { - fsCache.refresh(dstURLParsed.path, { level: -1 }); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.node.copied] ' + - 'for its merged target directory "' + dstURLParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - } else { - existing.invalidateFileContents(); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.node.copied] ' + - 'for its overwritten target file "' + dstURLParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - } - } else { - console.error('The type of the destination of a notification sys.fs.node.copied ' + - 'does not match that of the corresponding node in the fs-cache for "' + - dstURLParsed.path + '"'); - } - } else { - if (existing === null) { - root.putByAbsPath((isDir ? - pathUtil.attachSlash : - pathUtil.detachSlash)(dstURLParsed.path), - 'copied'); - topic.publish('#REQUEST.log', - 'Handled a notification [sys.fs.node.copied] ' + - 'for its target "' + dstURLParsed.path + '"'); - topic.publish('#REQUEST.log', ''); - } - } + // isFile + isFile : function (target, cb) { + var targetNode; + function callback(err, isFile) { + console.assert(targetNode === root.getByAbsPath(target), + 'Target node has been modified during a ' + + 'isFile request to server : ' + target); + if (err) { + cb(err); + } else { + if (withinCache(target)) { + console.assert(targetNode === undefined); + var added = root.putByAbsPath(target + (isFile ? '' : '/'), 'inferred'); + targetNode = added && added[added.length - 1]; + console.assert(targetNode); } - }); + cb(null, isFile); + } } - // TODO: need to publish fs.cache.node.copied? - // But the topic does not support the case when the source is out of the current file system. - }); - } + var ret = checkTargetPath(target, true); + if (isError(ret)) { + setTimeout(cb.bind(null, ret), 0); + } else { + targetNode = ret; + if (targetNode === undefined) { + mount.isFile(target, callback); + } else { + console.assert(targetNode); + setTimeout(cb.bind(null, null, targetNode instanceof File), 0); + } - function withinCache(path) { - if (path.indexOf('wfs://') === 0) { - // path is given in Webida File System URL - var pathParsed = parseWFSURL(path); - if (fsURLParsed.fsServer !== pathParsed.fsServer || - fsURLParsed.fsid !== pathParsed.fsid) { - return false; } - path = pathParsed.path; - } - return dirsToCache.some(function (cached) { return path.indexOf(cached) === 0; }); - } - function getName(obj) { return obj.name; } - function isDir(stat) { return stat.isDirectory; } - function isFile(stat) { return stat.isFile; } - function checkTargetPath(path, exists, allowsDirPath) { - if (!isValidAbsPath(path)) { - return 'The path "' + path + '" is not a valid absolute path'; - } + }, - var node = root.getByAbsPath(path); - if (typeof exists === 'boolean') { - if ((exists && node === null) || (!exists && node)) { - return 'The path "' + path + '" must be ' + (exists ? 'present' : 'absent'); + // list + /* + list : function (target, recursive, cb) { + if (typeof recursive === 'function') { + cb = recursive; + recursive = false; } - } - if (!allowsDirPath && pathUtil.isDirPath(path)) { - return 'The path "' + path + '" ends with a slash, which is disallowed'; - } + this.listEx(target, { recursive: recursive }, cb); + }, + */ - return node; - } + list : function (target, recursive, cb) { + var node; + function checkValidList() { + var ret = checkTargetPath(target, true, true); + if (isError(ret)) { + return ret; + } + node = ret; - //--------------------------- - //--------------------------- - // end of private methods of FSCacheInner - //--------------------------- - //--------------------------- - - //******************************* - // inner class of FSCacheInner: FSNode - //******************************* + if (node && !(node instanceof Directory)) { + return 'The path "' + target + '" does not represent a directory.'; + } - var FSNode = declare(null, { + return null; + } - constructor : function (parent, name) { - this.parent = parent; // null iff this is the root node - this.name = name; - this.metadata = {}; - this.metadataInvalidated = {}; - }, + function callback0(err, subnodes) { + console.assert(node === root.getByAbsPath(target), + 'Target node has been modified during a ' + + 'list request to server : ' + target); + if (err) { + cb(err); + } else { + node.updateSubnodes(subnodes.filter(isDir), true, 'fetched'); + node.updateSubnodes(subnodes.filter(isFile), false, 'fetched'); + node.fetchedSubnodes = true; + cb(null, subnodes); + node.listed = true; + } + } - setMetadata: function (key, value, caseStr) { - var origValue = this.metadata[key]; - this.metadata[key] = value; - this.metadataInvalidated[key] = false; + function callback1(err, subnodes) { + console.assert(node === root.getByAbsPath(target), + 'Target node has been modified during a ' + + 'list request to server : ' + target); + if (err) { + cb(err); + } else { + var added = root.putByAbsPath(target, 'inferred'); + node = added && added[added.length - 1]; + node.updateSubnodes(subnodes.filter(isDir), true, 'fetched'); + node.updateSubnodes(subnodes.filter(isFile), false, 'fetched'); + node.fetchedSubnodes = true; + cb(null, subnodes); + node.listed = true; + } + } - var path = this.getPath(); - switch (caseStr) { - case 'fetched': - onMetadataFetched(path, key); - break; - case 'written': - onMetadataSet(path, key, value !== origValue); - break; - case 'refreshed': - onMetadataRefreshed(path, key); - break; - default: - console.assert(false, 'Unreachable'); + if (typeof recursive === 'function') { + cb = recursive; + recursive = false; } - }, - refreshMetadata: function (key) { - var self = this; - Object.keys(this.metadata).forEach(function (k) { - if (key === undefined || key === k) { - var origVal = self.metadata[k]; - var path = self.getPath(); - mount.getMeta(path, k, function (err, newVal) { - if (err) { - console.error('Cannot get metadata ' + k + ' of ' + path + - ' from server: ' + err); + target = pathUtil.attachSlash(target); + var err = checkValidList(); + if (err) { + setTimeout(cb.bind(null, err), 0); + } else { + if (withinCache(target)) { + if (node) { + if (node.fetchedSubnodes && !recursive) { + //console.log('hina temp: list from fs-cache: ' + target); + setTimeout(cb.bind(null, null, node.list()), 0); + node.listed = true; } else { - if (origVal !== newVal) { - this.setMetadata(k, newVal, 'refreshed'); - } + //console.log('hina temp: list from server (case 0): ' + target); + mount.list(target, recursive, callback0); // case 0 } - }); + } else if (node === undefined) { + //console.log('hina temp: list from server (case 1): ' + target); + mount.list(target, recursive, callback1); // case 1 + } else { + console.assert(false, 'Unreachable'); + } + } else { + //console.log('hina temp: list from server (case 2): ' + target); + mount.list(target, recursive, cb); } - }); - }, - - getParentPath : function () { - return this.parent ? this.parent.getPath() : EMPTY_PATH; - }, - - getPath : function () { - if (!this._path) { - // memoize - this._path = this.computePath(); } - return this._path; - }, - - computePath : function () { - return this.getParentPath() + this.name; }, - getType : function () { - return TYPE_UNKNOWN; - }, - detach : function (movedTo) { - if (this.parent) { - var detached = this.parent.removeSubnode(this.name, movedTo); - if (this !== detached) { - throw new Error('Detached node is wrong.'); + // listEx + listEx : function (target, options, cb) { + var node; + function checkValidListEx() { + var ret = checkTargetPath(target, true, true); + if (isError(ret)) { + return ret; } - } else { - throw new Error('Cannot detach the root node'); - } - }, - - satisfyingCond : function (cond) { + node = ret; - if (cond.types) { - if (cond.types.indexOf(this.getType()) < 0) { - return false; + if (node && !(node instanceof Directory)) { + return 'The path "' + target + '" does not represent a directory.'; } - } - return true; - }, + if (options.dirOnly && options.fileOnly) { + return 'Cannot simultaneously be dirOnly and fileOnly'; + } - collectNodes : function (arr, cond) { - if (this.satisfyingCond(cond)) { - arr.push(this); + return null; } - }, - getListInfo : function () { - return { - name : this.name, - isDirectory : this instanceof Directory, - isFile : this instanceof File - }; - }, + function callback(err, subnodes) { + console.assert(node === root.getByAbsPath(target), + 'Target node has been modified during a ' + + 'list request to server : ' + target); + if (err) { + cb(err); + } else { + if (!node) { + var added = root.putByAbsPath(target, 'inferred'); + node = added && added[added.length - 1]; + } + node.updateSubnodes(subnodes.filter(isDir), true, 'fetched'); + node.updateSubnodes(subnodes.filter(isFile), false, 'fetched'); + node.fetchedSubnodes = true; - show : function (level) { // for debugging - var arr = []; - for (var i = 0; i < level; i++) { - arr.push('| '); + if (options.dirOnly) { + subnodes = subnodes.filter(isDir); + } else if (options.fileOnly) { + subnodes = subnodes.filter(isFile); + } + cb(null, subnodes); + node.listed = true; + } } - arr.push(this.name); - console.log(arr.join('')); - }, - - invalidateFileContents: function () { - console.error('assertion fail: unreachable'); - }, + if (typeof options === 'function') { + cb = options; + options = {}; + } - invalidateMetadata: function (key) { - var keys = Object.keys(this.metadataInvalidated); - var path = this.getPath(); - if (key === undefined) { - keys.forEach(function (k) { - if (this.metadata[k] !== undefined && - !this.metadataInvalidated[k]) { - this.metadataInvalidated[k] = true; - onMetadataInvalidated(path, k); + target = pathUtil.attachSlash(target); + var err = checkValidListEx(); + if (err) { + setTimeout(cb.bind(null, err), 0); + } else { + if (withinCache(target)) { + if (node && node.fetchedSubnodes && !options.recursive) { + //console.log('hina temp: list from fs-cache: ' + target); + var subnodes = node.list(); + if (options.dirOnly) { + subnodes = subnodes.filter(isDir); + } else if (options.fileOnly) { + subnodes = subnodes.filter(isFile); + } + setTimeout(cb.bind(null, null, subnodes), 0); + node.listed = true; + } else { + //console.log('hina temp: list from server (inside cache): ' + target); + // ignore dirOnly and fileOnly options. + mount.listEx(target, { recursive: options.recursive }, callback); // case 0 } - }); - } else if (keys.indexOf(key) >= 0) { - if (this.metadata[key] !== undefined && - !this.metadataInvalidated[key]) { - this.metadataInvalidated[key] = true; - onMetadataInvalidated(path, key); + } else { + //console.log('hina temp: list from server (outside cache): ' + target); + mount.listEx(target, options, cb); } - } else { - throw new Error('Metadata ' + key + ' is not set for "' + - this.getPath() + '"'); } - } - - }); - - //******************************* - // inner class of FSCacheInner: File - //******************************* - - var File = declare(FSNode, { - constructor : function (/*parent, name*/) { - this.contentInvalidated = false; }, - invalidateFileContents: function () { - if (this.content !== undefined && !this.contentInvalidated) { - this.contentInvalidated = true; - onFileInvalidated(this.getPath()); + // readFile + readFile : function (target, responseType, cb) { + var targetNode; + if (!cb) { + cb = responseType; + responseType = ''; } - }, + function checkValidReadFile() { + var ret = checkTargetPath(target, true); + if (isError(ret)) { + return ret; + } + targetNode = ret; - refreshFileContents : function () { + if (targetNode && !(targetNode instanceof File)) { + return 'The path "' + target + '" does not represent a file'; + } - // NOTE: refreshing contents is possible for invalidated contents too + return null; + } - var origContent = this.content; - if (origContent !== undefined) { - var path = this.getPath(); - var self = this; - mount.readFile(path, function (err, newContent) { - if (err) { - console.log('Cannot get content of ' + path + - ' from server. cannot refresh the file content (' + - err + ')'); + function callback(err, content) { + var targetNodeAfter = root.getByAbsPath(target); + console.assert(targetNode === targetNodeAfter, + 'Target node has been modified during a ' + + 'readFile request to server : ' + target + + ', before(' + targetNode + + '), after(' + targetNodeAfter + ')'); + if (err) { + cb(err); + } else { + if (targetNode === undefined) { + var added = root.putByAbsPath(target, 'inferred'); + targetNode = (added && added[added.length - 1]) || targetNodeAfter; + } + console.assert(targetNode); + + targetNode.setContent(content, 'fetched'); + cb(null, content); + } + } + + var err = checkValidReadFile(); + if (err) { + setTimeout(cb.bind(null, err), 0); + } else { + // FIXME: cache and compare responseType too + if (withinCache(target)) { + var content; + if (targetNode === undefined || + (content = targetNode.content) === undefined || + targetNode.contentInvalidated) { + mount.readFile(target, responseType, callback); } else { - if (origContent !== newContent) { - self.setContent(newContent, 'refreshed'); - } + console.assert(content !== null); + setTimeout(cb.bind(null, null, content), 0); } - }); + } else { + mount.readFile(target, responseType, cb); + } } }, - setContent : function (content, caseStr) { - var origContent = this.content; - this.content = content; - this.contentInvalidated = false; - - var path = this.getPath(); - switch (caseStr) { - case 'fetched': - onFileFetched(path); - break; - case 'written': - onFileWritten(path, content === undefined || content !== origContent); - break; - case 'refreshed': - onFileRefreshed(path); - break; - default: - console.assert(false, 'Unreachable'); + // searchFiles + searchFiles : function (keyword, where, options, cb) { + if (isValidAbsPath(where)) { + mount.searchFiles.apply(mount, arguments); + } else { + setTimeout(cb.bind(null, 'The path "' + where + '" is not a valid absolute path'), 0); } }, - getType : function () { - return TYPE_FILE; + // replaceFiles + replaceFiles : function (keyword, replace, where, options, cb) { + if (_.every(where, function (path) { return isValidAbsPath(path); })) { + mount.replaceFiles.apply(mount, arguments); + } else { + setTimeout(cb.bind(null, 'The list of where contains a string which ' + + 'is not a valid absolute path.'), 0); + } }, - getSummary : function () { - return this.name; + // stat + stat : function (pathList, cb) { + if (pathList.every(function (path) { return isValidAbsPath(path); })) { + mount.stat.apply(mount, arguments); + } else { + setTimeout(cb.bind(null, 'The list of paths contains a string which ' + + 'is not a valid absolute path.'), 0); + } } }); - //******************************* - // inner class of FSCacheInner: Directory - //******************************* - - var Directory = declare(FSNode, { - constructor : function (/*parent, name*/) { - this.dirs = new SortedArray('name'); - this.files = new SortedArray('name'); - }, - - invalidateFileContents: function () { - this.dirs.forEach(function (dir) { - dir.invalidateFileContents(); - }); - this.files.forEach(function (file) { - file.invalidateFileContents(); - }); - }, + //--------------------------- + //--------------------------- + // private methods of FSCacheInner + //--------------------------- + //--------------------------- - invalidateMetadata: function (key) { - FSNode.prototype.invalidateMetadata.call(this, key); - this.dirs.forEach(function (dir) { - dir.invalidateMetadata(key); - }); - this.files.forEach(function (file) { - file.invalidateMetadata(key); - }); - }, + //--------------------------- + // utility functions + //--------------------------- - refreshFileContents : function (level) { - if (typeof level !== 'number') { // TODO: remove this check when stabilized - throw new Error('assertion fail: unrechable'); - } + function parseWFSURL(fsURL) { + var uri = new URI(fsURL); + var ret = { + fsServer: uri.host(), + fsid: uri.segment(0) + }; + uri.segment(0, ''); // drop the fsid + ret.path = uri.path(true); - if (level) { - this.dirs.forEach(function (dir) { - dir.refreshFileContents(level - 1); - }); - this.files.forEach(function (file) { - file.refreshFileContents(); - }); + return ret; + } + function subscribeToNotificationsOnFS(fsCache) { + function isIgnoredNotification(data, checkedURLs) { + // ignore changed caused by myself + var mySessionID = webida.auth.getSessionID(); + if (!data.sid) { + console.error('The session id of a notification is unknown'); + return true; } - }, - refreshMetadata: function (level, key) { - if (typeof level !== 'number') { // TODO: remove this check when stabilized - throw new Error('assertion fail: unrechable'); + if (mySessionID === data.sid) { + console.log('notification ignored: changes by the current app'); + return true; } - FSNode.prototype.refreshMetadata.call(this, key); - if (level) { - this.dirs.forEach(function (dir) { - dir.refreshMetadata(level - 1, key); - }); - this.files.forEach(function (file) { - file.refreshMetadata(key); - }); + if (checkedURLs.every(function (url) { return !withinCache(data[url]); })) { + console.log('notification ignored: changes outside cache'); + return true; } - }, - computePath : function () { - return this.getParentPath() + this.name + '/'; - }, + return false; + } - putByRelPath : function (relPath, caseStr) { - console.assert(relPath, - 'Directory.putByRelPath() was called with a falsy argument'); + topic.subscribe('sys.fs.file.written', function (data) { + if (isIgnoredNotification(data, ['url'])) { + return; + } - var i = relPath.indexOf('/'); - if (i < 0) { - // base case - var file = this.putSubnode(relPath, false, caseStr); - return (file ? [file] : null); - } else { - console.assert(i > 0, 'i must be a positive integer'); - var subdirName = relPath.substring(0, i); - var subdir = this.putSubnode(subdirName, true, caseStr); - var nextPath = relPath.substr(i + 1); - if (nextPath) { - if (subdir) { - // newly added - var addedArr = subdir.putByRelPath(nextPath, caseStr); - addedArr.unshift(subdir); - return addedArr; - } else { - // already there - subdir = this.getSubnode(subdirName); - return subdir.putByRelPath(nextPath, caseStr); - } - } else { - // base case - return (subdir ? [subdir] : null); + var urlParsed = parseWFSURL(data.url); + var existing = root.getByAbsPath(urlParsed.path); + if (!existing) { + // file created + if (existing === null) { + root.putByAbsPath(urlParsed.path, 'file-written'); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.file.written] for "' + + urlParsed.path + '" (as a file creation)'); + topic.publish('#REQUEST.log', ''); } + } else if (existing.getType() === TYPE_FILE) { + // file written + existing.invalidateFileContents(); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.file.written] for "' + + urlParsed.path + '" (as a file cache invalidation)'); + topic.publish('#REQUEST.log', ''); + } else { + console.error('sys.fs.file.written arrived for a non-file "' + + urlParsed.path + '"'); } - }, - // If a subnode exists with that name and type, then do nothing and return null. - // Otherwise, create the subnode, add it, and return the added subnode. - putSubnode : function (name, isDir, caseStr) { - console.assert(name); - console.assert(name.indexOf('/') === -1); - - var subnode = this.getSubnode(name); - if (subnode && (subnode.isInstanceOf(Directory) === isDir)) { - return null; - } else { - if (subnode) { - console.warn('A subnode with the same name "' + name + - '" but with different type was detected while putting a ' + - (isDir ? 'directory' : 'file') + ' to "' + - this.getPath() + '"'); - return null; - } else { - var added = this.addSubnode(name, isDir, caseStr); - return added; - } + }); + topic.subscribe('sys.fs.file.deleted', function (data) { + if (isIgnoredNotification(data, ['url'])) { + return; } - }, - addSubnode : function (name, isDir, caseStr) { - if (this.getSubnode(name)) { // TODO: remove this check if code is stabilized - console.error('Unreachable: Cannot overwrite existing subnode ' + name + - ' of ' + this.getPath() + ' by addSubnode()'); - throw new Error('Unreachable: Cannot overwrite existing subnode ' + name + - ' of ' + this.getPath() + ' by addSubnode()'); - } else { - var C = isDir ? Directory : File; - var subnode = new C(this, name); - if (isDir) { - this.dirs.add(subnode); - } else { - this.files.add(subnode); - } - - var maybeCreated; - switch (caseStr) { - case 'cache-root': - case 'inferred': - case 'fetched': - case 'restored': - maybeCreated = false; - break; - case 'copied': - case 'moved': - case 'dir-created': - case 'zip-created': - case 'zip-extracted': - case 'file-written': - case 'refreshed': - maybeCreated = true; - break; - default: - console.assert(false, 'Unreachable'); + var urlParsed = parseWFSURL(data.url); + var existing = root.getByAbsPath(urlParsed.path); + if (!existing) { + if (existing === null) { + console.error('sys.fs.file.deleted arrived for an absent file "' + + urlParsed.path + '"'); } - - onNodeAdded(this.getPath(), name, subnode.getType(), maybeCreated, caseStr === 'moved'); - - return subnode; + } else if (existing.getType() === TYPE_FILE) { + existing.detach(); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.file.deleted] for "' + + urlParsed.path + '"'); + topic.publish('#REQUEST.log', ''); + } else { + console.error('sys.fs.file.deleted arrived for a non-file "' + + urlParsed.path + '"'); } - }, - getByRelPath: function (relPath) { - //console.log('hina temp: relPath = ' + relPath); - console.assert(relPath, - 'Directory.getByRelPath() was called ' + - 'with falsy argument'); + }); + topic.subscribe('sys.fs.dir.created', function (data) { + if (isIgnoredNotification(data, ['url'])) { + return; + } - var i = relPath.indexOf('/'); - if (i < 0) { - return this.getSubnode(relPath); - } else { - console.assert(i > 0, - 'Directory.getByRelPath() was called ' + - 'with an absolute path: ' + relPath); - var nextPath; - var subnodeName = relPath.substring(0, i); - var subnode = this.getSubnode(subnodeName); - if (subnode) { - nextPath = relPath.substr(i + 1); - if (nextPath) { - if (subnode instanceof Directory) { - return subnode.getByRelPath(nextPath); - } else { - return null; - } - } else { - return subnode; - } - } else { - return subnode; + var urlParsed = parseWFSURL(data.url); + var existing = root.getByAbsPath(urlParsed.path); + if (!existing) { + if (existing === null) { + root.putByAbsPath(pathUtil.attachSlash(urlParsed.path), 'dir-created'); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.dir.created] for "' + + urlParsed.path + '"'); + topic.publish('#REQUEST.log', ''); } + } else if (existing.getType() === TYPE_DIRECTORY) { + console.error('sys.fs.dir.created arrived for an existing directory "' + + urlParsed.path + '"'); + } else { + console.error('sys.fs.dir.created arrived for a non-directory "' + + urlParsed.path + '"'); + } + }); + topic.subscribe('sys.fs.dir.deleted', function (data) { + if (isIgnoredNotification(data, ['url'])) { + return; } - }, - getSubnode : function (name) { - var queried = { name: name }; - var ret = this.dirs.query(queried) || this.files.query(queried); - if (ret) { - return ret; + var urlParsed = parseWFSURL(data.url); + var existing = root.getByAbsPath(urlParsed.path); + if (!existing) { + if (existing === null) { + console.error('sys.fs.dir.deleted arrived for an absent directory "' + + urlParsed.path + '"'); + } + } else if (existing.getType() === TYPE_DIRECTORY) { + existing.detach(); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.dir.deleted] for "' + + urlParsed.path + '"'); + topic.publish('#REQUEST.log', ''); } else { - return this.fetchedSubnodes ? null : undefined; + console.error('sys.fs.dir.deleted arrived for a non-directory "' + + urlParsed.path + '"'); } - }, - - removeSubnode : function (name, movedTo) { - var ret = this.getSubnode(name); - if (ret) { - var isDir = ret instanceof Directory; - var arr = isDir ? this.dirs : this.files; - var i = arr.indexOf(ret); - Array.prototype.splice.call(arr, i, 1); - - onNodeDeleted(this.getPath(), name, ret.getType(), movedTo); + }); + topic.subscribe('sys.fs.node.intractableChanges', function (data) { + if (isIgnoredNotification(data, ['url'])) { + return; } - return ret; - }, - - getType : function () { - return TYPE_DIRECTORY; - }, - - isEmpty : function () { - return (this.dirs.length === 0) && (this.files.length === 0); - }, - - updateSubnodes : function (stats, isDir, caseStr) { - var subnodes = isDir ? this.dirs : this.files; - var names = subnodes.map(getName); - var newNames = stats.map(getName); - - //console.log('hina temp: names = ' + names); - //console.log('hina temp: newNames = ' + newNames); - var toAdd = _.difference(newNames, names); - var toDel = _.difference(names, newNames); - //console.log('hina temp: toAdd = ' + toAdd); - //console.log('hina temp: toDel = ' + toDel); - var self = this; - toDel.forEach(function (name) { - self.removeSubnode(name); - }); - toAdd.forEach(function (name) { - self.addSubnode(name, isDir, caseStr); - }); - }, - // refresh hierarchy level by level - refreshHierarchy : function (level, doWhenAllDone) { - //console.log('hina temp: entering refreshHierarchy dirPath = ' + this.getPath()); - - // NOTE: getByAbsPath() must be invoked on the root. - // Nodes (except for the root) can be detached at any time during an - // asynchronous method call. + var urlParsed = parseWFSURL(data.url); + var existing = root.getByAbsPath(urlParsed.path); + if (!existing) { + if (existing === null) { + console.error('sys.fs.dir.intractableChanges arrived for an absent directory "' + + urlParsed.path + '"'); + } + } else if (existing.getType() === TYPE_DIRECTORY) { + fsCache.refresh(urlParsed.path, { level: -1 }, function () { + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.node.intractableChanges] for "' + + urlParsed.path + '"'); + topic.publish('#REQUEST.log', ''); - if (level && this.fetchedSubnodes) { - var dirPath = this.getPath(); - var self = this; - mount.list(dirPath, false, function (err, stats) { + onNodeChanges(urlParsed.path); + }); + } else { + console.error('sys.fs.dir.intractable-changes arrived for a non-directory "' + + urlParsed.path + '"'); + } - var subnodeTypesDone = 0; - function oneTypeDone() { - subnodeTypesDone++; - //console.log('hina temp: oneTypeDone for ' + dirPath + ' ' + subnodeTypesDone + ' time '); - if (subnodeTypesDone === 2) { // two types (dirs and files) - //console.log('hina temp: ' + dirPath + ' is done'); - doWhenAllDone(); - } - } + }); + topic.subscribe('sys.fs.node.moved', function (data) { + if (isIgnoredNotification(data, ['srcURL', 'dstURL'])) { + return; + } + var dstURLParsed; + if (withinCache(data.dstURL)) { + dstURLParsed = parseWFSURL(data.dstURL); + mount.isDirectory(dstURLParsed.path, function (err, isDir) { if (err) { - console.warn('Error: FileSystem.list failed while refreshing "' + - dirPath + '" (' + err + ')'); - doWhenAllDone(); + console.log('Cannot figure out whether the destination "' + dstURLParsed.path + + '" of a notification sys.fs.node.moved is a directory or not: ' + err); + console.log('The notification is ignored.'); } else { - var newDirs = stats.filter(isDir); - self.updateSubnodes(newDirs, true, 'refreshed'); - - var subdirsToRefresh = self.dirs.length; - var subdirsRefreshed = 0; - if (subdirsToRefresh) { - self.dirs.forEach(function (dir) { - dir.refreshHierarchy(level - 1, function () { - subdirsRefreshed++; - if (subdirsRefreshed === subdirsToRefresh) { - //console.log('hina temp: subdirs of ' + dirPath + ' are done'); - oneTypeDone(); - } - }); - }); + var existing = root.getByAbsPath(dstURLParsed.path); + if (existing) { + if ((existing.getType() === TYPE_DIRECTORY) === isDir) { + if (isDir) { + console.error('sys.fs.node.moved arraived for an existing "' + + dstURLParsed.path + '" as its destination'); + } else { + existing.invalidateFileContents(); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.node.moved] ' + + 'for its overwritten target file "' + dstURLParsed.path + '"'); + topic.publish('#REQUEST.log', ''); + } + } else { + console.error('The type of the destination of a notification sys.fs.node.moved ' + + 'does not match that of the corresponding node in the fs-cache for "' + + dstURLParsed.path + '"'); + } } else { - oneTypeDone(); + if (existing === null) { + root.putByAbsPath((isDir ? + pathUtil.attachSlash : + pathUtil.detachSlash)(dstURLParsed.path), + 'moved'); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.node.moved] for its target "' + + dstURLParsed.path + '"'); + topic.publish('#REQUEST.log', ''); + } } - - var newFiles = stats.filter(isFile); - self.updateSubnodes(newFiles, false, 'refreshed'); - oneTypeDone(); } }); - } else { - doWhenAllDone(); } + if (withinCache(data.srcURL)) { + var srcURLParsed = parseWFSURL(data.srcURL); + var existing = root.getByAbsPath(srcURLParsed.path); + if (!existing) { + if (existing === null) { + console.error('sys.fs.node.moved arrived for an absent "' + + srcURLParsed.path + '" as its source'); + } + } else { + existing.detach(withinCache(data.dstURL) ? dstURLParsed.path : undefined); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.node.moved] for its source "' + + srcURLParsed.path + '"'); + topic.publish('#REQUEST.log', ''); + } + } - }, - - collectNodes : function (arr, cond) { - FSNode.prototype.collectNodes.call(this, arr, cond); // super call - this.dirs.forEach(function (dir) { - dir.collectNodes(arr, cond); - }); - this.files.forEach(function (file) { - file.collectNodes(arr, cond); - }); - }, - - list : function () { - if (this.fetchedSubnodes) { - var arr = []; + // TODO: need to publish fs.cache.node.moved? + // But the topic does not support the case when the source is out of the current file system. + }); + topic.subscribe('sys.fs.node.copied', function (data) { + if (isIgnoredNotification(data, ['dstURL'])) { + return; + } - this.dirs.forEach(function (subnode) { - arr.push(subnode.getListInfo()); + var existing; + var dstURLParsed = parseWFSURL(data.dstURL); + if (withinCache(dstURLParsed.path)) { + mount.isDirectory(dstURLParsed.path, function (err, isDir) { + if (err) { + console.log('Cannot figure out whether the destination "' + dstURLParsed.path + + '" of a notification sys.fs.node.copied is a directory or not: ' + err); + console.log('The notification is ignored.'); + } else { + existing = root.getByAbsPath(dstURLParsed.path); + if (existing) { + if ((existing.getType() === TYPE_DIRECTORY) === isDir) { + if (isDir) { + fsCache.refresh(dstURLParsed.path, { level: -1 }); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.node.copied] ' + + 'for its merged target directory "' + dstURLParsed.path + '"'); + topic.publish('#REQUEST.log', ''); + } else { + existing.invalidateFileContents(); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.node.copied] ' + + 'for its overwritten target file "' + dstURLParsed.path + '"'); + topic.publish('#REQUEST.log', ''); + } + } else { + console.error('The type of the destination of a notification sys.fs.node.copied ' + + 'does not match that of the corresponding node in the fs-cache for "' + + dstURLParsed.path + '"'); + } + } else { + if (existing === null) { + root.putByAbsPath((isDir ? + pathUtil.attachSlash : + pathUtil.detachSlash)(dstURLParsed.path), + 'copied'); + topic.publish('#REQUEST.log', + 'Handled a notification [sys.fs.node.copied] ' + + 'for its target "' + dstURLParsed.path + '"'); + topic.publish('#REQUEST.log', ''); + } + } + } }); + } - this.files.forEach(function (subnode) { - arr.push(subnode.getListInfo()); - }); + // TODO: need to publish fs.cache.node.copied? + // But the topic does not support the case when the source is out of the current file system. + }); + } - return arr; - } else { - console.error('Unreachable: list should not be called on ' + - 'a node which has never fetched subnodes: ' + this.getPath()); - throw new Error('Unreachable: list should not be called on ' + - 'a node which has never fetched subnodes: ' + this.getPath()); + function withinCache(path) { + if (path.indexOf('wfs://') === 0) { + // path is given in Webida File System URL + var pathParsed = parseWFSURL(path); + if (fsURLParsed.fsServer !== pathParsed.fsServer || + fsURLParsed.fsid !== pathParsed.fsid) { + return false; } - }, + path = pathParsed.path; + } + return dirsToCache.some(function (cached) { return path.indexOf(cached) === 0; }); + } + function getName(obj) { return obj.name; } + function isDir(stat) { return stat.isDirectory; } + function isFile(stat) { return stat.isFile; } + function checkTargetPath(path, exists, allowsDirPath) { + if (!isValidAbsPath(path)) { + return 'The path "' + path + '" is not a valid absolute path'; + } - show : function (level) { // for debugging - var arr = []; - for (var i = 0; i < level; i++) { - arr.push('| '); + var node = root.getByAbsPath(path); + if (typeof exists === 'boolean') { + if ((exists && node === null) || (!exists && node)) { + return 'The path "' + path + '" must be ' + (exists ? 'present' : 'absent'); } - arr.push(this.name + '/'); - console.log(arr.join('')); - - this.dirs.forEach(function (subdir) { - subdir.show(level + 1); - }); + } - this.files.forEach(function (subdir) { - subdir.show(level + 1); - }); - }, + if (!allowsDirPath && pathUtil.isDirPath(path)) { + return 'The path "' + path + '" ends with a slash, which is disallowed'; + } - getSummary : function () { - var subSummaries; - if (this.listed || !withinCache(this.getPath())) { - subSummaries = []; - this.dirs.forEach(function (dir) { - var val = dir.getSummary(); - console.assert(typeof val === 'object', - 'Summary of a subdir must be an object'); - subSummaries.push(val); - }); - this.files.forEach(function (file) { - var val = file.getSummary(); - console.assert(typeof val === 'string', - 'Summary of a file must be a string'); - subSummaries.push(val); - }); - } else { - subSummaries = null; - } + return node; + } - if (this.name) { - return { n: this.name, s: subSummaries }; - } else { - // only root can reach here - return subSummaries; - } - }, + //--------------------------- + //--------------------------- + // end of private methods of FSCacheInner + //--------------------------- + //--------------------------- - restoreFromSummary : function (subSummaries) { - if (subSummaries) { - console.assert(subSummaries instanceof Array, - 'SubSummaries must be an array'); - var self = this; - subSummaries.forEach(function (summary) { - var type = typeof summary; - if (type === 'object') { - var added = self.addSubnode(summary.n, true, 'restored'); - added.restoreFromSummary(summary.s); - } else if (type === 'string') { - self.addSubnode(summary, false, 'restored'); - } else { - console.assert(false, - 'Summary must be an object or string'); - } - }); - this.fetchedSubnodes = true; - } - } - }); //--------------------------- // event handlers @@ -2286,7 +2294,7 @@ function (webida, SortedArray, pathUtil, _, URI, declare, topic) { } // finally, the object - var fsCache = new FSCacheInner(); + fsCache = new FSCacheInner(); return fsCache; // NOTE: 'this' is ignored for the constructor FSCache. } diff --git a/common/src/webida/app.js b/common/src/webida/app.js index 7b93ffa6..d9cd3524 100644 --- a/common/src/webida/app.js +++ b/common/src/webida/app.js @@ -21,13 +21,13 @@ define([ 'dojo/topic', 'webida-lib/app-config', 'webida-lib/FSCache-0.1', - 'webida-lib/msg', + 'webida-lib/server-pubsub', 'webida-lib/plugin-manager-0.1', 'webida-lib/util/browserInfo', 'webida-lib/util/loading-screen', 'webida-lib/util/logger/logger-client', 'webida-lib/util/notify', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/dialogs/popup-dialog/PopupDialog', 'dojo/domReady!' ], function ( @@ -122,7 +122,11 @@ define([ } function saveStatusSync() { - logger.info('saveStatusSync()'); + if (webida.VERSION) { + logger.info('current server api does not support synchronous file writing'); + return null; + } + var statusString = getStatusStringToSave(); if (statusString) { var formData = new FormData(); @@ -307,7 +311,7 @@ define([ mount.readFile(path + lastStatusFile, function (err, content) { //logger.info('content = ', content); if (err) { - singleLogger.log('(C) not read last status file (' + err + ')'); + singleLogger.error('(C) could not read last status file', err); } else { try { appLastStatus = JSON.parse(content); @@ -363,6 +367,7 @@ define([ } function connectToConnServer() { + webida.auth.getMyInfo(function (e, data) { if (e) { logger.error('getMyInfo error: ' + e); @@ -372,8 +377,18 @@ define([ logger.error('failed to connect to conn server'); } else { logger.log('connected to conn server'); - - // sys.fs.change notification subscribe + + // note from webida-desktop + // new server-api-*.js has no acl service and does not require + // explicit subscription for default server events like fs.change + // So, msgAgent.init() actually do nothing but returns some dummy + // stuffs & real event processing will be handled without app.js + + if (typeof webida.acl !== 'object') { + logger.log('no need to subscribe and topic relay with new api '); + return; + } + webida.acl.getAuthorizedRsc('fs:readFile', function (err, topics) { if (err) { logger.error('getAuthorizedRsc: ', err); diff --git a/common/src/webida/msg.js b/common/src/webida/msg.js index df1d821d..58cc281c 100644 --- a/common/src/webida/msg.js +++ b/common/src/webida/msg.js @@ -33,7 +33,7 @@ * @module msg */ define([ - './webida-0.3', + 'webida-lib/server-api', 'external/socket.io-client/socket.io', 'webida-lib/util/logger/logger-client' ], function ( @@ -139,6 +139,26 @@ define([ } }; + var connMap = new HashMap(); + + var TaskMgr = function () { + var taskMap = new HashMap(); + this.pushTask = function (cb) { + var taskid = guid(); + taskMap.put(taskid, cb); + return taskid; + }; + + this.popTask = function (id, err, msg) { + var func = taskMap.get(id); + if (func) { + func(err, msg); + } + taskMap.remove(id); + }; + }; + + var taskMgr = new TaskMgr(); /** * In order to receive messages, you need to define callback functons as follow @@ -233,38 +253,16 @@ define([ ]; /* jshint camelcase: true */ - var guid = (function () { + function guid () { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } - return function () { - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); - }; - })(); - - - var TaskMgr = function () { - var taskMap = new HashMap(); - this.pushTask = function (cb) { - var taskid = guid(); - taskMap.put(taskid, cb); - return taskid; - }; - - this.popTask = function (id, err, msg) { - var func = taskMap.get(id); - if (func) { - func(err, msg); - } - taskMap.remove(id); - }; - }; + } - var taskMgr = new TaskMgr(); - var connMap = new HashMap(); /** diff --git a/common/src/webida/plugin-manager-0.1.js b/common/src/webida/plugin-manager-0.1.js index 61e5872b..01f3fe1a 100644 --- a/common/src/webida/plugin-manager-0.1.js +++ b/common/src/webida/plugin-manager-0.1.js @@ -23,7 +23,7 @@ * */ -define(['webida-lib/webida-0.3', +define(['webida-lib/server-api', 'external/lodash/lodash.min', 'external/URIjs/src/URI', 'dojo/promise/all', diff --git a/common/src/webida/plugins/command-system/command/command-factory.js b/common/src/webida/plugins/command-system/command/command-factory.js index 7c0048a8..70cb26c1 100644 --- a/common/src/webida/plugins/command-system/command/command-factory.js +++ b/common/src/webida/plugins/command-system/command/command-factory.js @@ -52,7 +52,7 @@ define([ return new Promise(function (resolve) { var registry = commandRegistry.getCommand(id); if (registry) { - require([registry.plugin + '/commands'], function (extension) { + require([registry.plugin + '/commands'], function (extension) { var Constructor = extension[toPascalCase(id) + 'Command']; if (!Constructor) { var changedId = id.split(':')[0]; diff --git a/common/src/webida/plugins/editors/DataSourceHandler.js b/common/src/webida/plugins/editors/DataSourceHandler.js index bb8df9e0..ee560fc3 100644 --- a/common/src/webida/plugins/editors/DataSourceHandler.js +++ b/common/src/webida/plugins/editors/DataSourceHandler.js @@ -54,7 +54,7 @@ define([ logger.info('new DataSourceHandler()'); /** @type {Object} */ - this.subscribed = []; + this.subscriptionHandles = []; /** @type {Array.} */ this.deletedNodesSet = []; this._subscribe(); @@ -78,11 +78,14 @@ define([ */ _subscribe: function () { //on deleted - this.subscribed.push(topic.subscribe('workspace/nodes/deleting', this._onNodesDeleted.bind(this))); - this.subscribed.push(topic.subscribe('fs/cache/node/deleted', this._checkCase.bind(this))); + this.subscriptionHandles.push(topic.subscribe('workspace/nodes/deleting', + this._onNodesDeleted.bind(this))); + this.subscriptionHandles.push(topic.subscribe('fs/cache/node/deleted', + this._checkCase.bind(this))); //on content changed - this.subscribed.push(topic.subscribe('remote/persistence/updated', this._onContentChange.bind(this))); + this.subscriptionHandles.push(topic.subscribe('remote/persistence/updated', + this._onContentChange.bind(this))); }, /** @@ -90,8 +93,8 @@ define([ * @protected */ _unsubscribe: function () { - this.subscribed.forEach(function (subscribed) { - subscribed.remove(); + this.subscriptionHandles.forEach(function (handle) { + handle.remove(); }); }, @@ -232,8 +235,11 @@ define([ //https://github.com/webida/webida-client/issues/670 //Changing dataSourceId as URI format var dsRegistry = _getWorkbench().getDataSourceRegistry(); - var dataSource = dsRegistry.getDataSourceById(dataSourceId.replace('wfs:/', '')); - _askReload(dataSource); + var dsid = dataSourceId.replace('wfs:', ''); + var dataSource = dsRegistry.getDataSourceById(dsid); + if (dataSource) { + _askReload(dataSource); + } }, /** diff --git a/common/src/webida/plugins/fs-commands/commands.js b/common/src/webida/plugins/fs-commands/commands.js index 4e152a34..4e16c4ac 100644 --- a/common/src/webida/plugins/fs-commands/commands.js +++ b/common/src/webida/plugins/fs-commands/commands.js @@ -1070,10 +1070,10 @@ define([ } }); - function DeleteCommand(id) { - DeleteCommand.id = id; + function DeleteFileCommand(id) { + DeleteFileCommand.id = id; } - genetic.inherits(DeleteCommand, Command, { + genetic.inherits(DeleteFileCommand, Command, { execute : function () { return new Promise(function (resolve) { wv.removeInteractively(); @@ -1099,6 +1099,6 @@ define([ CopyCommand: CopyCommand, CutCommand: CutCommand, PasteCommand: PasteCommand, - DeleteCommand: DeleteCommand + DeleteFileCommand: DeleteFileCommand }; }); diff --git a/common/src/webida/plugins/git/commands.js b/common/src/webida/plugins/git/commands.js index be89f47b..73e80d3d 100644 --- a/common/src/webida/plugins/git/commands.js +++ b/common/src/webida/plugins/git/commands.js @@ -59,7 +59,7 @@ define([ 'webida-lib/util/genetic', 'webida-lib/util/notify', 'webida-lib/util/path', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/dialogs/buttoned-dialog/ButtonedDialog', './git-core', './git-icon', @@ -6307,7 +6307,7 @@ define([ } genetic.inherits(GitRunCommand, Command, { execute: function (resolve) { - return new Promise(function () { + return new Promise(function (resolve) { var selectedPath = workspace.getSelectedPath(); if (selectedPath) { var gitRootPath = git.findGitRootPath(selectedPath); diff --git a/common/src/webida/plugins/git/git-commands.js b/common/src/webida/plugins/git/git-commands.js index cdb71eed..9d6038ad 100644 --- a/common/src/webida/plugins/git/git-commands.js +++ b/common/src/webida/plugins/git/git-commands.js @@ -57,7 +57,7 @@ define([ 'webida-lib/util/arrays/BubblingArray', 'webida-lib/util/notify', 'webida-lib/util/path', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'webida-lib/widgets/dialogs/buttoned-dialog/ButtonedDialog', './git-core', './git-icon', diff --git a/common/src/webida/plugins/resources/legacy-event.js b/common/src/webida/plugins/resources/legacy-event.js index 16490284..d29c3f7c 100644 --- a/common/src/webida/plugins/resources/legacy-event.js +++ b/common/src/webida/plugins/resources/legacy-event.js @@ -29,7 +29,7 @@ define([ 'dojo/topic', 'external/URIjs/src/URI', 'webida-lib/util/logger/logger-client', - 'webida-lib/webida-0.3' + 'webida-lib/server-api' ], function ( topic, URI, diff --git a/common/src/webida/plugins/resources/resource-event.js b/common/src/webida/plugins/resources/resource-event.js index 64ca2c8e..be9e2e7f 100644 --- a/common/src/webida/plugins/resources/resource-event.js +++ b/common/src/webida/plugins/resources/resource-event.js @@ -25,7 +25,7 @@ define([ 'dojo/topic', 'webida-lib/util/logger/logger-client', - 'webida-lib/webida-0.3' + 'webida-lib/server-api' ], function ( topic, Logger, @@ -61,7 +61,7 @@ define([ function getWfsUri(path) { if (typeof path === 'string') { - return 'wfs:/' + path; + return 'wfs:' + path; } else { return null; } diff --git a/common/src/webida/plugins/session-event-dispatcher/dispatch-legacy-resource-topics.js b/common/src/webida/plugins/session-event-dispatcher/dispatch-legacy-resource-topics.js new file mode 100644 index 00000000..05c50c4f --- /dev/null +++ b/common/src/webida/plugins/session-event-dispatcher/dispatch-legacy-resource-topics.js @@ -0,0 +1,135 @@ +/* +* Copyright (c) 2012-2015 S-Core Co., Ltd. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/** + * Legacy resource topic dispatcher for sys.fs.* and resource/*, remote/* events + * This module dispatches dojo topics from new server api events + * + * @since: 2016.06.30 + */ + +define([ + 'dojo/topic', + 'external/URIjs/src/URI', + 'webida-lib/util/logger/logger-client', + 'webida-lib/server-api' +], function ( + topic, + URI, + Logger, + webida +) { + 'use strict'; + + var logger = Logger.getSingleton(); + logger.debug = logger.log; + + // All event names that begin with '__' is now deprecated. + // New Event Bus will not publish __* events to any channels + + var LEGACY_FS_TOPIC_NAMES = { + // events generated by sever + addDir: 'sys.fs.dir.created', + unlinkDir: 'sys.fs.dir.deleted', + add:'sys.fs.file.written', // sys.fs.file.created seems to have no effects. + change:'sys.fs.file.written', + unlink:'sys.fs.file.deleted', + + // events generated by client, as a result of api call + _addDir: 'sys.fs.dir.created', + _unlinkDir: 'sys.fs.dir.deleted', + _add:'sys.fs.file.created', + _change:'sys.fs.file.written', + _unlink:'sys.fs.file.deleted', + + // fake events generated by client, as a result of api call + __refresh: 'sys.fs.node.interactableChanges', + __moved: 'sys.fs.node.moved', + __copied: 'sys.fs.node.copied', + __exec : 'sys.fs.node.interactableChanges' + }; + + var LEGACY_RESOURCES_TOPIC_NAMES = { + // events generated by sever + addDir: 'remote/directory/created', + unlinkDir: 'remote/directory/deleted', + add:'remote/persistence/created', + change:'remote/persistence/updated', + unlink:'remote/persistence/deleted', + + // events generated by client, as a result of api call + _addDir: 'resources/directory/created', + _unlinkDir: 'resources/directory/deleted', + _add:'resources/persistence/created', + _change:'resources/persistence/updated', + _unlink:'resources/persistence/deleted' + }; + + // we don't map 'remote' topics for now. + // editors + + function WfsEvent(wfsId, type, wfsPath) { + this.wfsId = wfsId; + this.type = type; + this.wfsPath = wfsPath; + } + + WfsEvent.prototype = { + getLegacyWfsUrl : function getLegacyWfsUrl() { + var wfsUriPath = this.wfsId + '/' + this.wfsPath; + var wfsUri = webida.info.serverUri.protocol('wfs').path(wfsUriPath).normalize(); + return wfsUri.toString(); + }, + + getResourceWfsUrn : function getLegacyWfsUrl() { + return 'wfs:' + '/' + this.wfsPath; // path should be absolute in urn + }, + + // reserved to next release - wfs events will have 'independent' channels per fsid + // getEventChannelName : function getEventChannelName() { + // return 'server/wfs/' + this.wfsId; + // }, + + getLegacyFsTopicData : function getLegacyFsTopicData(eventType) { + return { + sid: eventType[0] === '_' ? webida.info.sessionId : 'out-of-session', + url: this.getLegacyWfsUrl() + }; + } + }; + + //See https://github.com/webida/webida-developer-guide-english/wiki/Event-System + function dispatchLegacyResourceTopics() { + + // since no legacy events are using stats, we discard stats here + var wfsEvent = new WfsEvent(arguments[0], arguments[1], arguments[2]); + var legacyFsTopicName = LEGACY_FS_TOPIC_NAMES[wfsEvent.type]; + var legacyResourcesTopicName = LEGACY_RESOURCES_TOPIC_NAMES[wfsEvent.type]; + + if (legacyFsTopicName) { + var topicData = wfsEvent.getLegacyFsTopicData(wfsEvent.type); + logger.debug('dispatching ' + legacyFsTopicName, topicData); + topic.publish(legacyFsTopicName, topicData); + } + if (legacyResourcesTopicName) { + var topicUrn = wfsEvent.getResourceWfsUrn(); + logger.debug('dispatching ' + legacyResourcesTopicName, topicUrn); + topic.publish(legacyResourcesTopicName, topicUrn); + } + } + + return dispatchLegacyResourceTopics; +}); diff --git a/common/src/webida/plugins/session-event-dispatcher/plugin.js b/common/src/webida/plugins/session-event-dispatcher/plugin.js new file mode 100644 index 00000000..fd4f15a9 --- /dev/null +++ b/common/src/webida/plugins/session-event-dispatcher/plugin.js @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2012-2016 S-Core Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file handles server api events and publish client level topics/events + * @since 1.7.0 + * @author jh1977.kim@samsung.com + */ + +define([ + 'external/URIjs/src/URI', + 'dojo/topic', + 'webida-lib/server-api', + 'webida-lib/util/logger/logger-client', + './dispatch-legacy-resource-topics' +], function ( + URI, + topic, + webida, + Logger, + dispatchLegacyResourceTopics +) { + 'use strict'; + + var logger = Logger.getSingleton(); + logger.debug = logger.log; + var sessionEventSource = webida.sessionService.getEventSource(); + + function dispatchTopic(topicName) { + return function __reflectingTopicDispatcher(eventName) { + var args = []; + args.push(eventName); + for (var i=1; i < arguments.length; i++) { + args.push(arguments[i]); + } + logger.debug('reflecting event ' + eventName + ' to topic ' + topicName, args); + topic.publish.apply(topic, args); + }; + } + + sessionEventSource.on('session.announcement', dispatchTopic('server/session/announcement')); + sessionEventSource.on('session.closing', dispatchTopic('server/session/closing')); + + // following events are defined via socket.io & webida service client api does not change + // the event names for readability & simplicity. + sessionEventSource.on('connect', dispatchTopic('server/session/connect')); + sessionEventSource.on('disconnect', dispatchTopic('server/session/disconnect')); + sessionEventSource.on('reconnect', dispatchTopic('server/session/reconnect')); + sessionEventSource.on('connect_error', dispatchTopic('server/session/connect/error')); + sessionEventSource.on('connect_timeout', dispatchTopic('server/session/connect/timeout')); + sessionEventSource.on('reconnect_failed', dispatchTopic('server/session/reconnect/failed')); + + // workspace.wfs events come from WfsEventGate, not from server. + sessionEventSource.on('workspace.wfs', dispatchLegacyResourceTopics); + + // need some 'toasting' plugin for basic session events, but not here. + // for this plugins should work without toaster. + // we may need a 'session-manager' plugin, using workbench, toaster and other plugins + // to show & manage session-related user actions. + // for examples + // 1) pop-up login dialogs and send credentials to api + // 2) pop-up some warning message when server begins knight-fall protocol (closing) + // 3) pop-up some warning message when client lost session client connection to server + // 4) pop-up some 'off-line' warning and show some status bar message + + logger.debug('initialized session event dispatcher plugin'); + return {}; +}); diff --git a/common/src/webida/plugins/session-event-dispatcher/plugin.json b/common/src/webida/plugins/session-event-dispatcher/plugin.json new file mode 100644 index 00000000..41972abf --- /dev/null +++ b/common/src/webida/plugins/session-event-dispatcher/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "webida.common.session-event-dispatcher", + "description": "Plugin that handles server/service level events to publish plugin topics", + "version": "0.1.0", + "requirement": "" +} diff --git a/common/src/webida/plugins/workbench/views-controller.js b/common/src/webida/plugins/workbench/views-controller.js index 764d5e24..3dcbcfdd 100644 --- a/common/src/webida/plugins/workbench/views-controller.js +++ b/common/src/webida/plugins/workbench/views-controller.js @@ -29,7 +29,7 @@ define([ 'require', 'webida-lib/plugin-manager-0.1', 'dojox/layout/ToggleSplitter', // ToggleSplitter - 'webida-lib/webida-0.3', // webida + 'webida-lib/server-api', // webida 'webida-lib/app', // app 'webida-lib/widgets/views/viewmanager', // vm 'webida-lib/widgets/views/viewFocusController', // ViewFocusController @@ -57,15 +57,9 @@ define([ ) { 'use strict'; - topic.subscribe('view/unregistered', function (event) { - viewsController.focusController.unregisterView(event.view); - }); - topic.subscribe('view/maximize', function (event) { - viewsController.toggleFullScreen(event.location); - }); - var GROUPNAME = 'COMMON-VIEW'; var statusbarText; + var viewsController = { _leftSplitViewContainer : null, _rightSplitViewContainer : null, @@ -562,7 +556,7 @@ define([ aspect.before(leftSplitter, '_startDrag', function () { topic.publish('layout/pane/resized'); - }); + }); aspect.before(rightSplitter, '_handleOnChange', function () { topic.publish('layout/pane/resized'); @@ -570,7 +564,7 @@ define([ aspect.before(rightSplitter, '_startDrag', function () { topic.publish('layout/pane/resized'); - }); + }); aspect.before(bottomSplitter, '_handleOnChange', function () { topic.publish('layout/pane/resized'); @@ -578,7 +572,7 @@ define([ aspect.before(bottomSplitter, '_startDrag', function () { topic.publish('layout/pane/resized'); - }); + }); var vcList; @@ -636,6 +630,12 @@ define([ this.collapsePanel('bottom'); } + // FIXME : workbench should not depend on auth api. + // need refactoring to move api-centric jobs to model or separated controller + // let menu view have more flexible layout, including 'filler', + // and make uid plugin contribute to workbench in the usual way. + // current work-around is just skipping create uid-menu. + Webida.auth.getMyInfo(function (e, data) { if (e) { console.error('getMyInfo error: ' + e); @@ -648,54 +648,58 @@ define([ ], function ( commandSystem ) { - var menu = new DropDownMenu({style: 'display: none;' }); - var model = commandSystem.service.getUserIdMenuModel().items[0]; - model.items.forEach(function (item) { - var menuItem = new MenuItem({ - label: item.name, - onClick: function () { - commandSystem.service.requestExecution(item.commandId); - } + var parentModel = commandSystem.service.getUserIdMenuModel(); + // TODO : service should provide a 'clean' way to check menu model exists + if (parentModel && parentModel.id !== 'root') { + var menu = new DropDownMenu({style: 'display: none;' }); + var model = parentModel.items[0]; + model.items.forEach(function (item) { + var menuItem = new MenuItem({ + label: item.name, + onClick: function () { + commandSystem.service.requestExecution(item.commandId); + } + }); + menu.addChild(menuItem); }); - menu.addChild(menuItem); - }); - menu.startup(); - var button = new DropDownButton({ - label: data.email, - name: 'userinfo', - dropDown: menu, - id: 'userinfoButton' - }); - dom.byId('dropDownUserinfo').appendChild(button.domNode); + menu.startup(); + var button = new DropDownButton({ + label: data.email, + name: 'userinfo', + dropDown: menu, + id: 'userinfoButton' + }); + dom.byId('dropDownUserinfo').appendChild(button.domNode); + } }); } }); - - (function () { - var menu = new DropDownMenu({ style: 'display: none;' }); + + (function () { + var menu = new DropDownMenu({ style: 'display: none;' }); var exts = pm.getExtensions('webida.common.workbench:perspective') || []; var button; var defaultPerspectiveName = 'Default'; var perspectiveNamePrefix = 'Perspective: '; - var initialPerspectiveButtonLabel = perspectiveNamePrefix + defaultPerspectiveName; - + var initialPerspectiveButtonLabel = perspectiveNamePrefix + defaultPerspectiveName; + function getPerspectiveNameById(perspectiveId) { var name = defaultPerspectiveName; if (perspectiveId) { - exts.forEach(function (ext) { + exts.forEach(function (ext) { if (ext.id === perspectiveId) { name = ext.name; } - }); - } - return name; + }); + } + return name; } if (exts.length > 0) { menu.addChild(new MenuItem({ label: defaultPerspectiveName, onClick: function () { - button.set('label', initialPerspectiveButtonLabel); + button.set('label', initialPerspectiveButtonLabel); _self.currentPerspectiveID = null; } })); @@ -722,7 +726,7 @@ define([ dom.byId('dropDownPerspectiveinfo').appendChild(button.domNode); } })(); - }, + }, getActivatedPanel : function () { var _self = this; @@ -1073,15 +1077,15 @@ define([ if (panel) { _self.expandPanel(location); switch (location) { - case 'bottom' : - _self.lastPanelState.height = domStyle.get(panel.domNode, 'height'); - domStyle.set(panel.domNode, 'height', '100%'); - break; - case 'left' : - case 'right' : - _self.lastPanelState.width = domStyle.get(panel.domNode, 'width'); - domStyle.set(panel.domNode, 'width', '100%'); - break; + case 'bottom' : + _self.lastPanelState.height = domStyle.get(panel.domNode, 'height'); + domStyle.set(panel.domNode, 'height', '100%'); + break; + case 'left' : + case 'right' : + _self.lastPanelState.width = domStyle.get(panel.domNode, 'width'); + domStyle.set(panel.domNode, 'width', '100%'); + break; } } }, @@ -1091,13 +1095,13 @@ define([ var panel = _self._getPanel(location); if (panel) { switch (location) { - case 'bottom' : - domStyle.set(panel.domNode, 'height', _self.lastPanelState.height + 'px'); - break; - case 'left' : - case 'right' : - domStyle.set(panel.domNode, 'width', _self.lastPanelState.width + 'px'); - break; + case 'bottom' : + domStyle.set(panel.domNode, 'height', _self.lastPanelState.height + 'px'); + break; + case 'left' : + case 'right' : + domStyle.set(panel.domNode, 'width', _self.lastPanelState.width + 'px'); + break; } } }, @@ -1284,7 +1288,14 @@ define([ }); } - }; + }; // end of viewsController + + topic.subscribe('view/unregistered', function (event) { + viewsController.focusController.unregisterView(event.view); + }); + topic.subscribe('view/maximize', function (event) { + viewsController.toggleFullScreen(event.location); + }); return viewsController; }); diff --git a/common/src/webida/plugins/workspace/plugin.js b/common/src/webida/plugins/workspace/plugin.js index 797b4dab..7c207229 100644 --- a/common/src/webida/plugins/workspace/plugin.js +++ b/common/src/webida/plugins/workspace/plugin.js @@ -30,7 +30,7 @@ define([ //'webida-lib/plugins/workbench/preference-system/store', // TODO: issue #12055 'plugins/webida.preference/preference-service-factory', 'webida-lib/plugins/workbench/plugin', - 'webida-lib/webida-0.3', + 'webida-lib/server-api', 'dijit', 'dijit/registry', 'dijit/Tree', diff --git a/common/src/webida/server-pubsub-compat.js b/common/src/webida/server-pubsub-compat.js new file mode 100644 index 00000000..2e55c21f --- /dev/null +++ b/common/src/webida/server-pubsub-compat.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2012-2016 S-Core Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @module server-pubsub + * @fileoverview dummy api for legacy client using webida-0.3.js + * + * This module provides some sub-set of msgs.js, doing nothing + * @version: 0.1 + */ + +define([ + 'webida-lib/util/logger/logger-client' +], function ( + Logger +) { + 'use strict'; + + var logger = new Logger(); + // logger.setConfig('level', Logger.LEVELS.log); // for development mode only + //logger.off(); + + var User = function (nick, email, uid, token) { + this.nick = nick; + this.email = email; + this.uid = uid; + this.token = token; + }; + + var init = function messagingInit(uid, token, host, callbackFunctions, cb) { + + var user = new User('nick', 'email', uid, token); + logger.log('pubsub init#- new user', user); + + // dummy stuffs + window.setTimeout( function() { + cb(null, user); + }, 0); + }; + + var sub2 = function messagingSubscribe(user, topics, cb) { + // dummy stuffs + var subscriptionResult = { + 'topics': topics + }; + window.setTimeout( function() { + cb(subscriptionResult); + }, 0); + }; + + return { + init: init, + sub2: sub2 + }; +}); diff --git a/common/src/webida/util/loadCSSList.js b/common/src/webida/util/loadCSSList.js index 86b4e781..e1010348 100644 --- a/common/src/webida/util/loadCSSList.js +++ b/common/src/webida/util/loadCSSList.js @@ -39,7 +39,7 @@ define([ function checkDone(doneItems) { return function () { _.each(doneItems, function (newDone) { - if (_.contains(loadedItems, newDone)) { + if (_.includes(loadedItems, newDone)) { notify.error('Not good ' + newDone + ' in ' + loadedItems); } else { loadedItems.push(newDone); @@ -47,9 +47,9 @@ define([ }); _.each(doneItems, function (newDone) { _.each(deferredDones[newDone], function (listener) { - if (!_.contains(processedDones, listener.id)) { + if (!_.includes(processedDones, listener.id)) { var containsAll = _.every(listener.neededs, function (x) { - return _.contains(loadedItems, x); + return _.includes(loadedItems, x); }); if (containsAll) { processedDones.push(listener.id); @@ -111,7 +111,7 @@ define([ }); var toLoad = _.filter(items, function (x) { - return !_.contains(loadedItems, x) && !_.contains(loadingItems, x); + return !_.includes(loadedItems, x) && !_.includes(loadingItems, x); }); if (toLoad.length > 0) { loadingItems = loadingItems.concat(toLoad); diff --git a/common/src/webida/webida-0.3.js b/common/src/webida/webida-0.3.js index 73f73291..b84cab4e 100644 --- a/common/src/webida/webida-0.3.js +++ b/common/src/webida/webida-0.3.js @@ -29,8 +29,9 @@ /* global io: true */ define([ + 'external/URIjs/src/URI', 'text!top/site-config.json' -], function (siteConfigText) { +], function (URI, siteConfigText) { 'use strict'; var XHR = XMLHttpRequest || require('xmlhttprequest').XMLHttpRequest; @@ -2298,7 +2299,13 @@ define([ * @memberOf module:webida.FSService */ mod.FSService.prototype.mountByFSID = function (fsid) { - return new mod.FSService.FileSystem('wfs://webida/' + fsid); + var fsServerUri = new URI(mod.conf.fsServer); + var newWfsUri = fsServerUri + .protocol('wfs') + .path('/' + fsid) + .search('') + .toString(); + return new mod.FSService.FileSystem(newWfsUri); }; /** @@ -4237,7 +4244,7 @@ define([ token.issueTime = Date.now(); token.data = href.substring(startIndex, endIndex); mod.tokenGenerator.validateToken = function (/*token*/) { return true; }; - console.log(token.data, token.issueTime); + console.log("webida.js set personal token " + token.data, + " at " + token.issueTime); } } diff --git a/site-config-desktop.json b/site-config-desktop.json new file mode 100644 index 00000000..ed325796 --- /dev/null +++ b/site-config-desktop.json @@ -0,0 +1,23 @@ +{ + "app": { + "appId" : "webida-ide-desktop", + "baseUrl": false, + "dashboardBaseUrl" : "unused", + "apidocBaseUrl" : "will be generated automatically", + "oauth": { + "clientId": "webida-ide-desktop", + "redirectUrl": false + } + }, + + "// server" : " data will be filled from local storage, (webida-destktop-config item)", + "server": { }, + + "// build" : "will be re-written while packaging webida desktop with electron-packager", + "build" : { + "version" : "0.0.0", + "buildNumber": 0, + "buildTime": "1970-01-01 00:00:00", + "commitId": "unknown" + } +} \ No newline at end of file diff --git a/site-config-example.json b/site-config-example.json index bc58cfef..75522651 100644 --- a/site-config-example.json +++ b/site-config-example.json @@ -8,12 +8,6 @@ "clientId": "webida-ide-local-5000", "redirectUrl": "http://localhost:5000/webida-client/auth.html" }, - "build" : { - "version" : "0.0.0", - "buildNumber": 0, - "buildTime": "1970-01-01 00:00:00", - "commitId": "unknown" - }, "googleAnalytics" : false }, "server": { @@ -22,7 +16,7 @@ "fsServer": "http://fs.webida.net", "buildServer": "http://build.webida.net", "ntfServer": "http://ntf.webida.net", - "connServer": "http://build.webida.net", + "connServer": "http://conn.webida.net", "corsServer": "http://cors.webida.net", "deploy": { "type": "domain", @@ -30,5 +24,11 @@ }, "signUpEnable": false, "guestMode": true + }, + "build" : { + "version" : "0.0.0", + "buildNumber": 0, + "buildTime": "1970-01-01 00:00:00", + "commitId": "unknown" } } \ No newline at end of file diff --git a/site-config.json b/site-config.json index 0f970329..b3aab279 100644 --- a/site-config.json +++ b/site-config.json @@ -8,13 +8,7 @@ "clientId": "webida-ide-local-5000", "redirectUrl": "http://localhost:5000/webida-client/auth.html" }, - "build" : { - "version" : "1.7.0", - "buildNumber": 127, - "buildTime": "1970 01 01 00 00 00", - "commitId": "" - }, - "googleAnalytics" : "UA-68503782-1" + "googleAnalyics" : false }, "server": { "appServer": "https://app.webida.org", @@ -22,13 +16,19 @@ "fsServer": "https://fs.webida.org", "buildServer": "https://build.webida.org", "ntfServer": "https://ntf.webida.org", - "connServer": "https://build.webida.org", + "connServer": "https://conn.webida.org", "corsServer": "https://cors.webida.org", "deploy": { - "type": "domain", + "type": "path", "pathPrefix": "-" }, + "build" : { + "version" : "1.7.0", + "buildNumber": 127, + "buildTime": "1970 01 01 00 00 00", + "commitId": "" + }, "signUpEnable": false, - "guestMode": true + "guestMode": false } }