cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From agri...@apache.org
Subject [05/21] git commit: Refactored such that download works from cordova serve
Date Thu, 24 Oct 2013 01:55:44 GMT
Refactored such that download works from cordova serve


Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/9ae368ee
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/9ae368ee
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/9ae368ee

Branch: refs/heads/master
Commit: 9ae368eec38fd838321432b8d07cda3692f3ec0a
Parents: c46221f
Author: Andrew Grieve <agrieve@chromium.org>
Authored: Thu Oct 10 15:57:07 2013 -0400
Committer: Andrew Grieve <agrieve@chromium.org>
Committed: Wed Oct 23 21:55:17 2013 -0400

----------------------------------------------------------------------
 createproject.sh                         |   6 +-
 www/cdvah_index.html                     |   6 +-
 www/cdvah_js/AddCtrl.js                  |  53 ++--
 www/cdvah_js/AppConstants.js             |  12 -
 www/cdvah_js/AppsService.js              | 346 +++++++-------------------
 www/cdvah_js/KnownExtensionDownloader.js |  25 +-
 www/cdvah_js/ListCtrl.js                 |  30 ++-
 www/cdvah_js/Notify.js                   |   4 -
 www/cdvah_js/ResourcesLoader.js          | 198 ++++-----------
 www/cdvah_js/ServeCordovaJSHandler.js    |  78 ++++++
 www/cdvah_js/app.js                      |  16 ++
 www/cdvah_views/add.html                 |  39 ++-
 www/cdvah_views/list.html                |  12 +-
 www/config.xml                           |   1 +
 14 files changed, 296 insertions(+), 530 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/createproject.sh
----------------------------------------------------------------------
diff --git a/createproject.sh b/createproject.sh
index b386bc2..435ef9d 100755
--- a/createproject.sh
+++ b/createproject.sh
@@ -21,7 +21,10 @@ module.exports = function(grunt) {
   grunt.loadNpmTasks('grunt-contrib-watch');
 
   grunt.registerTask('prepare', 'Runs cdv prepare', function() {
-    cordova.prepare();
+    var done = this.async();
+    cordova.prepare(function(e) {
+      done(!e);
+    });
   });
 
   // Default task(s).
@@ -41,6 +44,7 @@ $CORDOVA plugin add ../../../mobile_chrome_apps/zip
 $CORDOVA plugin add ../../../BarcodeScanner # https://github.com/wildabeast/BarcodeScanner.git
 $CORDOVA plugin add ../../cordova-plugin-file
 $CORDOVA plugin add ../../cordova-plugin-file-transfer
+$CORDOVA plugin add ../../cordova-labs/file-extras
 
 
 exit 0

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_index.html
----------------------------------------------------------------------
diff --git a/www/cdvah_index.html b/www/cdvah_index.html
index 1d2065c..9f0f82d 100644
--- a/www/cdvah_index.html
+++ b/www/cdvah_index.html
@@ -6,22 +6,18 @@
         <script type="text/javascript" src="cdvah_js/libs/q.min.js"></script>
         <script type="text/javascript" src="cdvah_js/libs/angular.min.js"></script>
         <script type="text/javascript" src="cdvah_js/app.js"></script>
-        <script type="text/javascript" src="cdvah_js/AppConstants.js"></script>
         <script type="text/javascript" src="cdvah_js/ContextMenuInjectScript.js"></script>
         <script type="text/javascript" src="cdvah_js/ServeCordovaJSHandler.js"></script>
         <script type="text/javascript" src="cdvah_js/ResourcesLoader.js"></script>
         <script type="text/javascript" src="cdvah_js/AppsService.js"></script>
         <script type="text/javascript" src="cdvah_js/AppBundleAlias.js"></script>
-        <script type="text/javascript" src="cdvah_js/KnownExtensionDownloader.js"></script>
-        <script type="text/javascript" src="cdvah_js/CdvhPackageHandler.js"></script>
-        <script type="text/javascript" src="cdvah_js/CrxPackageHandler.js"></script>
         <script type="text/javascript" src="cdvah_js/ListCtrl.js"></script>
         <script type="text/javascript" src="cdvah_js/AddCtrl.js"></script>
         <script type="text/javascript" src="cdvah_js/Notify.js"></script>
         <link rel="stylesheet" type="text/css" href="css/topcoat-mobile-light.min.css" />
         <link rel="stylesheet" type="text/css" href="css/style.css" />
     </head>
-    <body ng-app="CordovaAppHarness">
+    <body>
         <div class="topcoat-navigation-bar">
             <div class="topcoat-navigation-bar__item center full">
                 <h1 class="topcoat-navigation-bar__title">{{ appTitle }}</h1>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/AddCtrl.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/AddCtrl.js b/www/cdvah_js/AddCtrl.js
index 54e73b0..e004f21 100644
--- a/www/cdvah_js/AddCtrl.js
+++ b/www/cdvah_js/AddCtrl.js
@@ -1,62 +1,41 @@
 (function(){
     "use strict";
     /* global myApp */
-    myApp.controller("AddCtrl", ["notifier", "$rootScope", "$scope", "$location", "$window", "AppsService", function (notifier, $rootScope, $scope, $location, $window, AppsService) {
+    myApp.controller("AddCtrl", ["notifier", "$rootScope", "$scope", "$window", "AppsService", function (notifier, $rootScope, $scope, $window, AppsService) {
 
         $rootScope.appTitle = 'Add App';
 
         $scope.appData = {
-            appName : "",
-            appSource : "pattern",
-            appSourcePattern : "",
-            appSourceServe  : ""
+            appUrl : 'localhost',
+            installerType: 'serve'
         };
 
         $scope.addApp = function() {
-            var serviceCall;
-            if($scope.appData.appSource === "pattern") {
-                if(!$scope.appData.appSourcePattern) {
-                    notifier.error('Url of package not specified');
-                    return;
-                }
-                serviceCall = AppsService.addAppFromPattern($scope.appData.appName, $scope.appData.appSourcePattern);
-            } else if($scope.appData.appSource === "serve") {
-                if(!$scope.appData.appSourceServe) {
-                    notifier.error('Url of config file not specified');
-                    return;
-                }
-                serviceCall = AppsService.addAppFromServe($scope.appData.appName, $scope.appData.appSourceServe);
-            }
+            var serviceCall = AppsService.addApp($scope.appData.installerType, $scope.appData.appUrl);
 
-            if(serviceCall){
-                serviceCall.then(function() {
-                    console.log('successfully installed');
-                    notifier.success('Successfully installed');
-                }, function(error) {
-                    console.error(error);
-                    notifier.error('Unable to add application because: ' + error.message);
-                });
-            } else {
-                notifier.error('Error adding application: Unrecognized application source: ' + $scope.appData.appSource);
-            }
+            serviceCall.then(function(handler) {
+                console.log('successfully installed');
+                notifier.success('Successfully installed');
+                return AppsService.updateApp(handler)
+                .done();
+            }, function(error) {
+                console.error(error);
+                notifier.error('Unable to add application because: ' + error.message);
+            });
         };
 
         // True if the optional barcodescanner plugin is installed.
-        $scope.qr_enabled = !!$window.barcodescanner;
+        $scope.qr_enabled = !!(cordova.plugins && cordova.plugins.barcodeScanner);
 
         // Scans a QR code, placing the URL into the currently selected of source and pattern.
         $scope.fetchQR = function() {
             console.log('calling');
-            $window.barcodescanner.scan(function(result) {
+            $window.cordova.plugins.barcodeScanner.scan(function(result) {
                 console.log('success');
                 if (!result || result.cancelled || !result.text) {
                     notifier.error('No QR code received.');
                 } else {
-                    if ($scope.appData.appSource == 'pattern') {
-                        $scope.appData.appSourcePattern = result.text;
-                    } else {
-                        $scope.appData.appSourceServe = result.text;
-                    }
+                    $scope.appData.appUrl = result.text;
                     notifier.success('QR code received');
                     $scope.$apply();
                 }

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/AppConstants.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/AppConstants.js b/www/cdvah_js/AppConstants.js
deleted file mode 100644
index 81f8b4d..0000000
--- a/www/cdvah_js/AppConstants.js
+++ /dev/null
@@ -1,12 +0,0 @@
-(function() {
-    var TEMP_DIRECTORY = "cordova_app_harness_tempDir/";
-    var INSTALL_DIRECTORY = "cordova_app_harness_installed_apps/";
-    var APPS_JSON = "cordova_app_harness_installed_apps/apps.json";
-    var METADATA_JSON = "cordova_app_harness_installed_apps/metadata.json";
-
-    /* global myApp */
-    myApp.value("TEMP_DIRECTORY", TEMP_DIRECTORY);
-    myApp.value("INSTALL_DIRECTORY", INSTALL_DIRECTORY);
-    myApp.value("APPS_JSON", APPS_JSON);
-    myApp.value("METADATA_JSON", METADATA_JSON);
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/AppsService.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/AppsService.js b/www/cdvah_js/AppsService.js
index 0e59e3b..cd565d0 100644
--- a/www/cdvah_js/AppsService.js
+++ b/www/cdvah_js/AppsService.js
@@ -1,77 +1,73 @@
 (function() {
     "use strict";
     /* global myApp */
-    myApp.factory("AppsService", [ "ResourcesLoader", "INSTALL_DIRECTORY", "TEMP_DIRECTORY", "APPS_JSON", "METADATA_JSON", function(ResourcesLoader, INSTALL_DIRECTORY, TEMP_DIRECTORY, APPS_JSON, METADATA_JSON) {
+    myApp.factory("AppsService", [ "ResourcesLoader", "INSTALL_DIRECTORY", "APPS_JSON", function(ResourcesLoader, INSTALL_DIRECTORY, APPS_JSON) {
 
-        var platformId = cordova.require("cordova/platform").id;
-        // downloaders that know how to download from certain patterns.
-        // Eg: The KnownExtensionDownloader MAY know how to download from any uri's that end in known extensions
-        var downloadHandlers = [];
-        // handlers that have registered to unpack certain extensions during the installation of an app
-        var extensionHandlers = {};
+        var platformId = cordova.platformId;
+        // Map of type -> handler.
+        var _installHandlerFactories = {};
         // functions to run before launching an app
         var preLaunchHooks = [];
 
-        function grabExtensionFromUri(uri) {
-            var lastSegment = uri.split("#")[0].split("?")[0].split("/").pop();
-            var dotLocation = lastSegment.lastIndexOf(".");
-            var extension = (dotLocation !== -1)? lastSegment.substring(dotLocation + 1) : "";
-            return extension;
+        var _installHandlers = null;
+        var _lastLaunchedAppId = null;
+
+        function createInstallHandlersFromJson(json) {
+            var appList = json['appList'] || [];
+            var ret = [];
+            for (var i = 0, entry; entry = appList[i]; ++i) {
+                var factory = _installHandlerFactories[entry['appType']];
+                var handler = factory.createFromJson(entry['appUrl'], entry['appId']);
+                handler.lastUpdated = entry['lastUpdated'] && new Date(entry['lastUpdated']);
+                ret.push(handler);
+            }
+            return ret;
         }
 
-        function addNewAppFromPattern(appName, appSourcePattern) {
-            var fullFilePath;
-
-            return ResourcesLoader.deleteDirectory(INSTALL_DIRECTORY + appName)
-            .then(function(){
-                for(var i = 0; i < downloadHandlers.length; i++){
-                    if(downloadHandlers[i].handler.canHandleSourcePattern(appSourcePattern)){
-                        return downloadHandlers[i].handler.downloadFromPattern(appName, appSourcePattern, TEMP_DIRECTORY);
-                    }
-                }
-                throw new Error("App Harness does not know how to install an app from the pattern: " + appSourcePattern);
-            })
-            .then(function(_fullFilePath){
-                fullFilePath = _fullFilePath;
-                return ResourcesLoader.ensureDirectoryExists(INSTALL_DIRECTORY + appName);
-            })
-            .then(function(directoryPath){
-                var extension = grabExtensionFromUri(fullFilePath);
-                if(!extensionHandlers[extension]) {
-                    throw new Error("No handler for extension " + extension + " found");
-                }
-                return extensionHandlers[extension].extractPackageToDirectory(appName, fullFilePath, directoryPath);
-            })
-            .then(function(){
-                return registerApp(appName, "pattern", appSourcePattern);
+        function readAppsJson() {
+            var deferred = Q.defer();
+            ResourcesLoader.readJSONFileContents(APPS_JSON)
+            .then(function(result) {
+                deferred.resolve(result);
+            }, function() {
+                // Error means first run.
+                deferred.resolve({});
             });
+            return deferred.promise;
         }
 
-        function addNewAppFromServe(appName, appSourceServe){
-            return ResourcesLoader.deleteDirectory(INSTALL_DIRECTORY + appName)
-            .then(function(){
-                return ResourcesLoader.ensureDirectoryExists(INSTALL_DIRECTORY + appName + "/" + platformId);
-            })
-            .then(function(){
-                return registerApp(appName, "serve", appSourceServe);
+        function initHandlers() {
+            if (_installHandlers) {
+                return Q();
+            }
+
+            return readAppsJson()
+            .then(function(appsJson) {
+                _lastLaunchedAppId  = appsJson['lastLaunched'];
+                _installHandlers = createInstallHandlersFromJson(appsJson);
             });
         }
 
-        function registerApp(appName, appSource, appSourceData) {
-            return ResourcesLoader.readJSONFileContents(APPS_JSON)
-            .then(function(result){
-                result.installedApps = result.installedApps || [];
-                result.installedApps.push({
-                    "Name" :  appName,
-                    "Source" : appSource,
-                    "Data" : appSourceData,
-                    "Installed" : (new Date()).toLocaleString()
+        function writeAppsJson() {
+            var appsJson = {
+                'lastLaunched': _lastLaunchedAppId,
+                'appList': []
+            };
+            var appList = appsJson['appList'];
+            for (var i = 0, handler; handler = _installHandlers[i]; ++i) {
+                appList.push({
+                    'appId' : handler.appId,
+                    'appType' : handler.type,
+                    'appUrl' : handler.url,
+                    'lastUpdated': handler.lastUpdated && +handler.lastUpdated
                 });
-                return ResourcesLoader.writeJSONFileContents(APPS_JSON, result);
-            });
+            }
+
+            var stringContents = JSON.stringify(appsJson);
+            return ResourcesLoader.writeFileContents(APPS_JSON, stringContents);
         }
 
-        function isPathAbsolute(path){
+        function isUrlAbsolute(path){
             return (path.match(/^[a-z0-9+.-]+:/) != null);
         }
 
@@ -93,7 +89,7 @@
                             var el = els[i];
                             var srcValue = el.getAttribute("src");
                             if(srcValue) {
-                                if(isPathAbsolute(srcValue)) {
+                                if(isUrlAbsolute(srcValue)) {
                                     startLocation = srcValue;
                                 } else {
                                     srcValue = srcValue.charAt(0) === "/" ? srcValue.substring(1) : srcValue;
@@ -109,110 +105,19 @@
             });
         }
 
-        function removeApp(appName){
-            var entry;
-            return ResourcesLoader.ensureDirectoryExists(APPS_JSON)
-            .then(function() {
-                return ResourcesLoader.readJSONFileContents(APPS_JSON);
-            })
-            .then(function(result){
-                result.installedApps = result.installedApps || [];
-                for(var i = 0; i < result.installedApps.length; i++){
-                    if(result.installedApps[i].Name === appName) {
-                        entry = result.installedApps.splice(i, 1)[0];
-                        break;
-                    }
-                }
-                if(!entry) {
-                    throw new Error("The app " + appName + " was not found.");
-                }
-                return ResourcesLoader.writeJSONFileContents(APPS_JSON, result);
-            })
-            .then(function(){
-                return ResourcesLoader.deleteDirectory(INSTALL_DIRECTORY + appName);
-            })
-            .then(function(){
-                return entry;
-            });
-        }
-
-        function getAppsList(getFullEntries){
-            return ResourcesLoader.ensureDirectoryExists(APPS_JSON)
-            .then(function() {
-                return ResourcesLoader.readJSONFileContents(APPS_JSON);
-            })
-            .then(function(result){
-                result.installedApps = result.installedApps || [];
-                var newAppsList = [];
-
-                for(var i = 0; i < result.installedApps.length; i++){
-                    if(getFullEntries) {
-                        newAppsList.push(result.installedApps[i]);
-                    } else {
-                        newAppsList.push(result.installedApps[i].Name);
-                    }
-                }
-
-                return newAppsList;
-            });
-        }
-
-        function isUniqueApp(appName){
-            return getAppsList(false /* App names only */)
-            .then(function(appsList){
-                if(appsList.indexOf(appName) !== -1) {
-                    throw new Error("An app with this name already exists");
-                }
-            });
-        }
-
-        function getAppEntry(appName){
-            return getAppsList(true /* Get full app entry */)
-            .then(function(appEntries){
-                var entry;
-                for(var i = 0; i < appEntries.length; i++){
-                    if(appEntries[i].Name === appName){
-                        entry = appEntries[i];
-                        break;
-                    }
-                }
-                if(!entry){
-                    throw new Error("Could not find the app " + appName + " in the installed apps");
-                }
-                return entry;
-            });
-        }
-
         // On success, this function returns the following paths
         // appInstallLocation - INSTALL_DIR/app/platform/
         // platformWWWLocation - location containing the html, css and js files
         // configLocation - location of config.xml
         // startLocation - the path of the page to start the app with
-        function getAppPathsForAppEntry(entry){
+        function getAppPathsForHandler(handler) {
             var appPaths = {};
-            return ResourcesLoader.getFullFilePath(INSTALL_DIRECTORY + entry.Name + "/" + platformId)
-            .then(function(platformLocation){
-                if(!isPathAbsolute(platformLocation)){
-                    // assume file uri
-                    platformLocation = "file://" + platformLocation;
-                }
-                appPaths.appInstallLocation = platformLocation;
-                if(entry.Source === "pattern"){
-                    appPaths.platformWWWLocation = platformLocation + "/www/";
-                    appPaths.configLocation = platformLocation + "/config.xml";
-                } else if(entry.Source === "serve"){
-                    var configFile = entry.Data;
-                    var location = configFile.indexOf("/config.xml");
-                    if(location === -1){
-                        throw new Error("The location of config.xml provided is invalid. Expected the location to end with 'config.xml'");
-                    }
-                    //grab path including upto last slash
-                    var appLocation =  configFile.substring(0, location + 1);
-                    appPaths.platformWWWLocation = appLocation;
-                    appPaths.configLocation = configFile;
-                } else {
-                    throw new Error("Unknown app source: " + entry.Source);
-                }
+            var installPath = INSTALL_DIRECTORY + '/' + handler.appId;
+            appPaths.appInstallLocation = installPath;
+            appPaths.configLocation = installPath + "/config.xml";
+            appPaths.platformWWWLocation = installPath + "/www/";
+
+            return Q.fcall(function(){
                 return getAppStartPageFromConfig(appPaths.configLocation, appPaths.platformWWWLocation);
             })
             .then(function(startLocation){
@@ -233,26 +138,21 @@
         }
 
         return {
-            //return promise with the array of apps
-            getAppsList : function(getFullEntries) {
-                return getAppsList(getFullEntries);
+            // return promise with the array of apps
+            getAppList : function() {
+                return initHandlers()
+                .then(function() {
+                    return _installHandlers.slice();
+                });
             },
 
-            launchApp : function(appName) {
+            launchApp : function(handler) {
                 var appEntry;
                 var startLocation;
-                return ResourcesLoader.readJSONFileContents(METADATA_JSON)
-                .then(function(settings){
-                    settings = settings || {};
-                    settings.lastLaunched = appName;
-                    return ResourcesLoader.writeJSONFileContents(METADATA_JSON, settings);
-                })
+                _lastLaunchedAppId = handler.appId;
+                return writeAppsJson()
                 .then(function(){
-                    return getAppEntry(appName);
-                })
-                .then(function(_appEntry){
-                    appEntry = _appEntry;
-                    return getAppPathsForAppEntry(appEntry);
+                    return getAppPathsForHandler(handler);
                 })
                 .then(function(appPaths){
                     startLocation = appPaths.startLocation;
@@ -269,101 +169,45 @@
                 });
             },
 
-            addAppFromPattern : function(appName, appSourcePattern) {
-                return isUniqueApp(appName)
-                .then(function(){
-                    return addNewAppFromPattern(appName, appSourcePattern);
-                });
-            },
-
-            addAppFromServe : function(appName, appSourceServe) {
-                return ResourcesLoader.readFileContents(appSourceServe)
-                .then(function(contents){
-                    if(!contents){
-                        throw new Error("The contents of config.xml at " + appSourceServe + " could not be read.");
-                    }
-                })
-                .then(null, function(error){
-                    // Code reaches here either because the get request to config.xml fails or config.xml is empty
-                    var continueInstall = confirm("The contents of config.xml at " + appSourceServe + " could not be read.\n" +
-                    "Have you run cordova serve?\n\n" +
-                    "Press OK to continue the install process. Note that the config.xml should be visible before you start the app.");
-                    if(!continueInstall){
-                        throw error;
-                    }
-                })
-                .then(function(){
-                    return isUniqueApp(appName);
-                })
-                .then(function(){
-                    return ResourcesLoader.deleteDirectory(INSTALL_DIRECTORY + appName);
+            addApp : function(installerType, appUrl) {
+                var handlerFactory = _installHandlerFactories[installerType];
+                return Q.fcall(function(){
+                    return handlerFactory.createFromUrl(appUrl);
                 })
-                .then(function(){
-                    return addNewAppFromServe(appName, appSourceServe);
+                .then(function(handler) {
+                    _installHandlers.push(handler);
+                    return writeAppsJson()
+                    .then(function() {
+                        return handler;
+                    });
                 });
             },
 
-            uninstallApp : function(appName) {
-                return removeApp(appName);
-            },
-
-            getLastRunApp : function() {
-                return ResourcesLoader.readJSONFileContents(METADATA_JSON)
-                .then(function(settings){
-                    if(!settings || !settings.lastLaunched) {
-                        throw new Error("No App has been launched yet");
-                    }
-                    return settings.lastLaunched;
+            uninstallApp : function(handler) {
+                _installHandlers.splice(_installHandlers.indexOf(handler), 1);
+                return writeAppsJson()
+                .then(function() {
+                    var installPath = INSTALL_DIRECTORY + '/' + handler.appId;
+                    return ResourcesLoader.deleteDirectory(installPath);
                 });
             },
 
-            registerPatternDownloader : function(handler, priority){
-                if(!handler) {
-                    throw new Error("Expected handler");
-                }
-                if(typeof(handler.canHandleSourcePattern) !== "function") {
-                    throw new Error("Expected function for bool handler.canHandleSourcePattern(string pattern) to exist");
-                }
-                if(typeof(handler.downloadFromPattern) !== "function") {
-                    throw new Error("Expected function for (string fullFilePath or QPromise) handler.downloadFromPattern(string appName, string pattern, string tempDirectory) to exist");
-                }
-                if(!priority) {
-                    // Assign a default priority
-                    priority = 500;
-                }
-                insertObjectAtPriority(downloadHandlers, handler, priority);
-            },
-
-            registerPackageHandler : function(extension, handler) {
-                if(!extension) {
-                    throw new Error("Expcted extension");
-                }
-                if(!handler || typeof(handler.extractPackageToDirectory) !== "function") {
-                    throw new Error("Expected function for void handler.extractPackageToDirectory(string fullFilePath, string directoryPath) to exist");
-                }
-                if(handler[extension]) {
-                    throw new Error("Handler already exists for the extension: " + extension);
-                }
-                extensionHandlers[extension] = handler;
+            getLastRunApp : function() {
+                throw new Error('Not implemented.');
             },
 
-            updateApp : function(appName){
-                return removeApp(appName)
-                .then(function(entry){
-                    if(entry.Source === "pattern") {
-                        return addNewAppFromPattern(entry.Name, entry.Data);
-                    }
+            updateApp : function(handler){
+                return Q.fcall(function() {
+                    var installPath = INSTALL_DIRECTORY + '/' + handler.appId;
+                    return handler.updateApp(installPath);
                 });
             },
 
-            getKnownExtensions : function() {
-                return Object.keys(extensionHandlers);
+            registerInstallHandlerFactory : function(handlerFactory) {
+                _installHandlerFactories[handlerFactory.type] = handlerFactory;
             },
 
             addPreLaunchHook : function(handler, priority){
-                if(!handler || typeof(handler) !== "function") {
-                    throw new Error("Expected (QPromise or void) function(appName, wwwLocation) for handler");
-                }
                 if(!priority) {
                     // Assign a default priority
                     priority = 500;
@@ -372,4 +216,4 @@
             }
         };
     }]);
-})();
\ No newline at end of file
+})();

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/KnownExtensionDownloader.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/KnownExtensionDownloader.js b/www/cdvah_js/KnownExtensionDownloader.js
index cec413a..1274ef1 100644
--- a/www/cdvah_js/KnownExtensionDownloader.js
+++ b/www/cdvah_js/KnownExtensionDownloader.js
@@ -9,35 +9,14 @@
             return ret;
         }
 
-        function grabExtensionFromUri(uri) {
-            var lastSegment = uri.split("#")[0].split("?")[0].split("/").pop();
-            var dotLocation = lastSegment.lastIndexOf(".");
-            var extension = (dotLocation !== -1)? lastSegment.substring(dotLocation + 1) : "";
-            return extension;
-        }
-
         // Note the priority given has no meaning in and of itself. It is used solely to compare if any other component has higher priority.
-        AppsService.registerPatternDownloader({
-            canHandleSourcePattern : function (pattern) {
-                var canHandle = false;
-                if(isUri(pattern)) {
-                    var currentExtension = grabExtensionFromUri(pattern);
-                    if(currentExtension) {
-                        var knownExtensions = AppsService.getKnownExtensions();
-                        if(knownExtensions.indexOf(currentExtension) !== -1){
-                            canHandle = true;
-                        }
-                    }
-                }
-                return canHandle;
-            },
-
+        AppsService.registerInstallHandler({
             downloadFromPattern : function (appName, pattern, tempDirectory) {
                 var extension = grabExtensionFromUri(pattern);
                 var fileName = tempDirectory + appName + "." + extension;
                 return ResourcesLoader.downloadFromUrl(pattern, fileName);
             }
-        }, 500 /* assign a priority */);
+        });
 
     }]);
 })();

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/ListCtrl.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/ListCtrl.js b/www/cdvah_js/ListCtrl.js
index 3940554..928fb48 100644
--- a/www/cdvah_js/ListCtrl.js
+++ b/www/cdvah_js/ListCtrl.js
@@ -2,10 +2,11 @@
     "use strict";
     /* global myApp */
     myApp.controller("ListCtrl", [ "notifier", "$rootScope", "$scope", "$location", "$routeParams", "AppsService", function (notifier, $rootScope, $scope, $location, $routeParams, AppsService) {
-
-        $scope.appsList = [];
+        $scope.appList = [];
         $rootScope.appTitle = 'Cordova App Harness';
 
+        initialise();
+
         function clearAppBundleAliases(){
             var deferred = Q.defer();
             var appBundle = cordova.require("AppBundle.AppBundle");
@@ -61,19 +62,17 @@
         }
 
         $scope.loadAppsList = function(callApply) {
-            return AppsService.getAppsList(true /* get full information about the app */)
+            return AppsService.getAppList()
             .then(function(newAppsList){
                 newAppsList.sort(function(a, b){
-                    if(a.Name < b.Name) {
+                    if (a.appId < b.appId) {
                         return -1;
-                    } else if(a.Name > b.Name) {
+                    } else if(a.appId > b.appId) {
                         return 1;
                     }
                     return 0;
                 });
-                //clear the old apps list
-                $scope.appsList.splice(0, $scope.appsList.length);
-                angular.extend($scope.appsList, newAppsList);
+                $scope.appList = newAppsList;
                 if(callApply) {
                     $scope.$apply();
                 }
@@ -87,7 +86,7 @@
         $scope.launchApp = function(app){
             return AppsService.launchApp(app)
             .then(null, function(error){
-                console.error("Error during loading of app " + app + ": " + error);
+                console.error("Error during loading of app " + app.appId + ": " + error);
                 notifier.error("Something went wrong during the loading of the app. Please try again." + error);
             });
         };
@@ -98,23 +97,22 @@
                 notifier.success("Updated successfully");
                 console.log('successfully updated');
             }, function(error){
-                console.error("Error during updating of app " + app + ": " + error);
+                console.error("Error during updating of app " + app.appId + ": " + error);
                 notifier.error("Something went wrong during the updating of the app. Please try again.");
             });
         };
 
         $scope.removeApp = function(app) {
-            var shouldUninstall = confirm("Are you sure you want to uninstall " + app + "?");
+            var shouldUninstall = confirm("Are you sure you want to uninstall " + app.appId + "?");
             if(shouldUninstall) {
                 return AppsService.uninstallApp(app)
-                .then(function() { $scope.loadAppsList(true); }, function(error){
-                    console.error("Error during uninstall of app " + app + ": " + error);
+                .then(function() { $scope.loadAppsList(true); },
+                      function(error) {
+                    console.error(error);
                     notifier.error("Something went wrong during the uninstall of the app. Please try again.");
                 });
             }
-        };
-
-        document.addEventListener("deviceready", initialise, false);
+        }
     }]);
 })();
 

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/Notify.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/Notify.js b/www/cdvah_js/Notify.js
index 921d15d..e76dc4b 100644
--- a/www/cdvah_js/Notify.js
+++ b/www/cdvah_js/Notify.js
@@ -14,10 +14,6 @@
                         scope.notification = {};
                         scope.notification.message = newValue.message;
                         scope.notification.css = 'notification-' + newValue.type;
-
-                        $timeout(function() {
-                            $rootScope.notification = undefined;
-                        }, 5000);
                     }
                 });
             }

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/ResourcesLoader.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/ResourcesLoader.js b/www/cdvah_js/ResourcesLoader.js
index e6c5ae8..9a3e1ea 100644
--- a/www/cdvah_js/ResourcesLoader.js
+++ b/www/cdvah_js/ResourcesLoader.js
@@ -3,61 +3,29 @@
 
     /* global myApp */
     myApp.factory("ResourcesLoader", [ "$window", function ($window) {
-        var fs;
-        var initialised = false;
+        var rootDir;
 
         function initialiseFileSystem() {
-            var deferred = Q.defer();
-
-            if(!initialised) {
-
-                var failedFileSystemLookUp = function (error) {
-                    var errorString = "An error occurred while reading the file system.";
-                    if(error) {
-                        errorString += " " + JSON.stringify(error);
-                    }
-                    deferred.reject(new Error(errorString));
-                };
-
-                var success = function(_fs) {
-                    fs = _fs;
-                    initialised = true;
-                    deferred.resolve(fs);
-                };
-
-                try {
-                    $window.requestFileSystem($window.LocalFileSystem.PERSISTENT, 0, success, failedFileSystemLookUp);
-                } catch (e) {
-                    failedFileSystemLookUp(e);
-                }
-            } else {
-                deferred.resolve(fs);
-            }
-
-            return deferred.promise;
+            // HACK: Need to discuss better way to get the root entry.
+            return Q(rootDir = new $window.DirectoryEntry('/', '/'));
         }
 
         //promise returns full path to downloaded file
         function downloadFromUrl(url, fullFilePath) {
             var deferred = Q.defer();
 
-            try {
-                var downloadFail = function(error) {
-                    var str = "There was an error while downloading the file " + JSON.stringify(error);
-                    deferred.reject(new Error(str));
-                };
+            var downloadFail = function(error) {
+                var str = "There was an error while downloading the file " + JSON.stringify(error);
+                deferred.reject(new Error(str));
+            };
 
-                var downloadSuccess = function(fileEntry) {
-                    deferred.resolve(fileEntry.fullPath);
-                };
+            var downloadSuccess = function(fileEntry) {
+                deferred.resolve(fileEntry.fullPath);
+            };
 
-                var fileTransfer = new $window.FileTransfer();
-                fileTransfer.download(url, fullFilePath, downloadSuccess, downloadFail);
-            } catch(e) {
-                deferred.reject(new Error(e));
-            } finally {
-                return deferred.promise;
-            }
+            var fileTransfer = new $window.FileTransfer();
+            fileTransfer.download(url, fullFilePath, downloadSuccess, downloadFail);
+            return deferred.promise;
         }
 
         function trim(str) {
@@ -83,41 +51,31 @@
         function getDirectoryEntry(directoryName) {
             var deferred = Q.defer();
 
-            try {
-                var errorWhileGettingDirectoryEntry = function(error) {
-                    var str = "There was an error while getting the directory entry for directory " + directoryName + " " + JSON.stringify(error);
-                    deferred.reject(new Error(str));
-                };
-                var success = function(directoryEntry) {
-                    deferred.resolve(directoryEntry);
-                };
-                fs.root.getDirectory(directoryName, {create: true, exclusive: false}, success, errorWhileGettingDirectoryEntry);
-            } catch(e) {
-                deferred.reject(new Error(e));
-            } finally {
-                return deferred.promise;
-            }
+            var errorWhileGettingDirectoryEntry = function(error) {
+                var str = "There was an error while getting the directory entry for directory " + directoryName + " " + JSON.stringify(error);
+                deferred.reject(new Error(str));
+            };
+            var success = function(directoryEntry) {
+                deferred.resolve(directoryEntry);
+            };
+            rootDir.getDirectory(directoryName, {create: true, exclusive: false}, success, errorWhileGettingDirectoryEntry);
+            return deferred.promise;
         }
 
         //promise returns the file entry
         function getFileEntry(fileName, createFlag) {
             var deferred = Q.defer();
 
-            try {
-                var errorWhileGettingFileEntry = function(error) {
-                    var str = "There was an error while getting the file entry for file " + fileName + " " + JSON.stringify(error);
-                    deferred.reject(new Error(str));
-                };
-                var success = function(fileEntry) {
-                    deferred.resolve(fileEntry);
-                };
-                // !! - ensures a boolean value
-                fs.root.getFile(fixFilePath(fileName), {create: !!createFlag, exclusive: false}, success, errorWhileGettingFileEntry);
-            } catch(e) {
-                deferred.reject(new Error(e));
-            } finally {
-                return deferred.promise;
-            }
+            var errorWhileGettingFileEntry = function(error) {
+                var str = "There was an error while getting the file entry for file " + fileName + " " + JSON.stringify(error);
+                deferred.reject(new Error(str));
+            };
+            var success = function(fileEntry) {
+                deferred.resolve(fileEntry);
+            };
+            // !! - ensures a boolean value
+            rootDir.getFile(fixFilePath(fileName), {create: !!createFlag, exclusive: false}, success, errorWhileGettingFileEntry);
+            return deferred.promise;
         }
 
         //promise returns the file
@@ -126,28 +84,18 @@
             then(function(fileEntry){
                 var deferred = Q.defer();
 
-                try {
-                    var errorWhileGettingFile = function(error) {
-                        var str = "There was an error while getting the file for file " + fileName + " " + JSON.stringify(error);
-                        deferred.reject(new Error(str));
-                    };
+                var errorWhileGettingFile = function(error) {
+                    var str = "There was an error while getting the file for file " + fileName + " " + JSON.stringify(error);
+                    deferred.reject(new Error(str));
+                };
 
-                    fileEntry.file(deferred.resolve, errorWhileGettingFile);
-                } catch(e) {
-                    deferred.reject(new Error(e));
-                } finally {
-                    return deferred.promise;
-                }
+                fileEntry.file(deferred.resolve, errorWhileGettingFile);
+                return deferred.promise;
             });
         }
 
         function truncateToDirectoryPath(path) {
-            //remove the filename if it exists
-            var lastLevelIndex = path.search(/[\w ]+(\.[\w ]+)+$/g);
-            if(lastLevelIndex !== -1) {
-                path = path.substring(0, lastLevelIndex);
-            }
-            return path;
+            return path.replace(/\/[^\/]+$/, '/');
         }
 
         function getPathSegments(path){
@@ -174,7 +122,7 @@
                 deferred.reject(new Error(str));
             };
 
-            fs.root.getDirectory(directory, {create: true, exclusive: false}, gotDirEntry, failedToGetDirEntry);
+            rootDir.getDirectory(directory, {create: true, exclusive: false}, gotDirEntry, failedToGetDirEntry);
             return deferred.promise;
         }
 
@@ -229,7 +177,7 @@
                     if(xhr.status === 200) {
                         deferred.resolve(xhr);
                     } else {
-                        deferred.reject("XHR return status: " + xhr.status + " for url: " + url);
+                        deferred.reject(new Error("XHR return status: " + xhr.status + " for url: " + url));
                     }
                 }
             };
@@ -259,57 +207,8 @@
                 });
             },
 
-            // promise returns full path to file
-            getFullFilePath : function(filePath) {
-                return initialiseFileSystem()
-                .then(function(){
-                    var deferred = Q.defer();
-
-                    // Use the file's parent folder to get the full path
-                    var directory = filePath;
-                    var fileName = "";
-
-                    //remove the filename if it exists
-                    var lastLevelIndex = directory.search(/\/[\w ]+\.[\w ]+$/g);
-                    if(lastLevelIndex !== -1) {
-                        directory = filePath.substring(0, lastLevelIndex);
-                        fileName = filePath.substring(lastLevelIndex + 1);
-                    }
-
-                    //we need the directory name w.r.t the root, so remove any slashes in the beginning
-                    if(directory.charAt(0) === "/") {
-                        directory = directory.substring(1);
-                    }
-
-                    var gotFullPath = function(dirEntry) {
-                        var fullFilePath = dirEntry.fullPath + "/" + fileName;
-                        deferred.resolve(fullFilePath);
-                    };
-
-                    var failedToGetFullPath = function(error) {
-                        var str = "There was an error getting the full path of file: " + filePath + " " + JSON.stringify(error);
-                        deferred.reject(new Error(str));
-                    };
-
-                    fs.root.getDirectory(directory, {create: true, exclusive: false}, gotFullPath, failedToGetFullPath);
-                    return deferred.promise;
-                });
-            },
-
             // returns a promise with a full path to the downloaded file
-            downloadFromUrl : function(url, filePath) {
-                var self = this;
-                return initialiseFileSystem()
-                .then(function(){
-                    return self.ensureDirectoryExists(filePath);
-                })
-                .then(function(){
-                    return self.getFullFilePath(filePath);
-                })
-                .then(function(fullFilePath){
-                    return downloadFromUrl(url, fullFilePath);
-                });
-            },
+            downloadFromUrl : downloadFromUrl,
 
             //returns a promise with the contents of the file
             readFileContents : function(fileName) {
@@ -382,17 +281,6 @@
                 });
             },
 
-            //returns a promise when json file is written
-            writeJSONFileContents : function(fileName, contents) {
-                var stringContents;
-                if(typeof contents === "string") {
-                    stringContents = contents;
-                } else {
-                    stringContents = JSON.stringify(contents);
-                }
-                return this.writeFileContents(fileName, stringContents);
-            },
-
             deleteDirectory : function(directoryName) {
                 return initialiseFileSystem()
                 .then(function(){
@@ -435,4 +323,4 @@
         };
     }]);
 
-})();
\ No newline at end of file
+})();

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/ServeCordovaJSHandler.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/ServeCordovaJSHandler.js b/www/cdvah_js/ServeCordovaJSHandler.js
index 55bad69..eb8beed 100644
--- a/www/cdvah_js/ServeCordovaJSHandler.js
+++ b/www/cdvah_js/ServeCordovaJSHandler.js
@@ -2,6 +2,7 @@
     "use strict";
     /* global myApp */
     myApp.run(["AppsService", "ResourcesLoader", "ContextMenuInjectScript", function(AppsService, ResourcesLoader, ContextMenuInjectScript){
+        var platformId = cordova.platformId;
 
         AppsService.addPreLaunchHook(function(appEntry, appInstallLocation , wwwLocation) {
             if(appEntry.Source === "serve"){
@@ -21,5 +22,82 @@
                 });
             }
         }, 250 /* Give it a priority */);
+
+        function ServeHandler(url, appId) {
+            this.url = url;
+            this.appId = appId || '';
+            this.lastUpdated = null;
+            this._cachedProjectJson = null;
+            this._cachedConfigXml = null;
+        }
+
+        ServeHandler.prototype.type = 'serve';
+
+        ServeHandler.prototype._updateAppMeta = function() {
+            var self = this;
+            return ResourcesLoader.xhrGet(this.url + '/' + platformId + '/project.json')
+            .then(function(xhr) {
+                self._cachedProjectJson = JSON.parse(xhr.responseText);
+                return ResourcesLoader.xhrGet(self.url + self._cachedProjectJson['configPath']);
+            })
+            .then(function(xhr) {
+                self._cachedConfigXml = xhr.responseText;
+                var configXml = new DOMParser().parseFromString(self._cachedConfigXml, 'text/xml');
+                self.appId = configXml.firstChild.getAttribute('id');
+            });
+        };
+
+        // TODO: update should be more atomic. Maybe download to a new directory?
+        ServeHandler.prototype.updateApp = function(installPath) {
+            var self = this;
+            return this._updateAppMeta()
+            .then(function() {
+                var wwwPath = self._cachedProjectJson['wwwPath'];
+                var files = self._cachedProjectJson['wwwFileList'];
+                var i = 0;
+                function downloadNext() {
+                    if (!files[i]) {
+                        self.lastUpdated = new Date();
+                        return;
+                    }
+                    console.log('now downloading ' + i + ' of ' + files.length);
+                    var sourceUrl = self.url + wwwPath + files[i];
+                    var destPath = installPath + '/www' + files[i];
+                    console.log(destPath);
+                    i += 1;
+                    return ResourcesLoader.downloadFromUrl(sourceUrl, destPath).then(downloadNext);
+                }
+                return ResourcesLoader.ensureDirectoryExists(installPath + '/config.xml')
+                .then(function() {
+                    return ResourcesLoader.writeFileContents(installPath + '/config.xml', self._cachedConfigXml);
+                })
+                .then(downloadNext);
+            });
+        };
+
+        function createFromUrl(url) {
+            // Strip platform and trailing slash if they exist.
+            url = url.replace(/\/$/, '').replace(new RegExp(platformId + '$'), '').replace(/\/$/, '');
+            if (!/^http:/.test(url)) {
+                url = 'http://' + url;
+            }
+            if (!/:(\d)/.test(url)) {
+                url = url.replace(/(.*?\/\/[^\/]*)/, '$1:8000');
+            }
+            // Fetch config.xml.
+            var ret = new ServeHandler(url);
+
+            return ret._updateAppMeta().then(function() { return ret; });
+        }
+
+        function createFromJson(url, appId) {
+            return new ServeHandler(url, appId);
+        }
+
+        AppsService.registerInstallHandlerFactory({
+            type: 'serve',
+            createFromUrl: createFromUrl, // returns a promise.
+            createFromJson: createFromJson // does not return a promise.
+        });
     }]);
 })();

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_js/app.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/app.js b/www/cdvah_js/app.js
index 105a10b..c40bda7 100644
--- a/www/cdvah_js/app.js
+++ b/www/cdvah_js/app.js
@@ -1,4 +1,9 @@
+
+Q.longStackSupport = true;
+
 var myApp = angular.module("CordovaAppHarness", []);
+
+
 myApp.config(["$routeProvider", function($routeProvider){
     $routeProvider.when("/", {
         templateUrl: "cdvah_views/list.html",
@@ -9,3 +14,14 @@ myApp.config(["$routeProvider", function($routeProvider){
         controller: "AddCtrl"
     });
 }]);
+
+// foo
+document.addEventListener('deviceready', function() {
+    cordova.plugins.fileextras.getDataDirectory(false, function(dirEntry) {
+        var path = dirEntry.fullPath;
+        myApp.value("INSTALL_DIRECTORY", path + "/apps");
+        myApp.value("APPS_JSON", path + "/apps.json");
+        alert('booting strap');
+        angular.bootstrap(document, ['CordovaAppHarness']);
+    });
+}, false);

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_views/add.html
----------------------------------------------------------------------
diff --git a/www/cdvah_views/add.html b/www/cdvah_views/add.html
index a2602c8..d49ef54 100644
--- a/www/cdvah_views/add.html
+++ b/www/cdvah_views/add.html
@@ -1,25 +1,24 @@
 <form name="addForm" ng-controller="AddCtrl">
-    <label for="inputAppName">Enter the App Name</label><br />
-    <input class="topcoat-text-input" type="text" name="appName" ng-model="appData.appName" ng-pattern="/^[\w ]*$/" ng-maxlength="64" required />
-    <span ng-show="addForm.appName.$error.required">Required</span>
-    <span ng-show="addForm.appName.$error.pattern">Invalid characters used</span>
-    <span ng-show="addForm.appName.$error.maxlength">Too long</span>
-    <br />
-
-    <input type="radio" ng-model="appData.appSource" name="appSource" value="pattern" ng-checked="appData.appSource=='pattern'" />
-        <label for="inputAppPattern">Enter the URL to a file</label><br />
-        <input class="topcoat-text-input" id="inputAppPattern" type="text" name="appSourcePattern" ng-model="appData.appSourcePattern" ng-disabled="appData.appSource!='pattern'" ng-required="appData.appSource=='pattern'" autocorrect="off" autocapitalize="off" />
-        <span ng-show="addForm.appSourcePattern.$error.required">Required</span>
-        <br />
-    <input type="radio" ng-model="appData.appSource" name="appSource" value="serve" ng-checked="appData.appSource=='serve'" />
-        <label for="inputAppServe">Enter the URL to the server hosting the app</label><br />
-        <input class="topcoat-text-input" id="inputAppServe" type="text" name="appSourceServe" ng-model="appData.appSourceServe" ng-disabled="appData.appSource!='serve'" ng-pattern="/.*\/config.xml(\?.*|#.*)?$/" ng-required="appData.appSource=='serve'" autocorrect="off" autocapitalize="off" />
-        <span ng-show="addForm.appSourceServe.$error.required">Required</span>
-        <span ng-show="addForm.appSourceServe.$error.pattern">Url must point to a config.xml file</span>
-        <br />
+    <label>How to retrieve the app:<br>
+        <select ng-model="appData.installerType" />
+            <option value="serve">cordova serve</option>
+            <option value="cdvh">.cdvh file</option>
+        </select>
+    </label>
+    <div ng-show="appData.installerType == 'serve'">
+      <label>Enter server URL<br>
+          <input class="topcoat-text-input" type="text" ng-model="appData.appUrl" autocorrect="off" autocapitalize="off" />
+          <button class="topcoat-button" ng-click="fetchQR(appData, 'appUrl')" ng-show="qr_enabled">Scan QR Code</button><br>
+      </label>
+    </div>
+    <div ng-show="appData.installerType == 'cdvh'">
+      <label>Enter URL<br>
+          <input class="topcoat-text-input" type="text" ng-model="appData.appUrl" autocorrect="off" autocapitalize="off" />
+          <button class="topcoat-button" ng-click="fetchQR(appData, 'appUrl')" ng-show="qr_enabled">Scan QR Code</button><br>
+      </label>
+    </div>
     <div class="buttons">
-        <button class="topcoat-button" ng-click="fetchQR()" ng-show="qr_enabled">Scan QR Code</button><br />
-        <button class="topcoat-button--cta" ng-click="addApp()" ng-disabled="!(addForm.$valid)">Add</button>
+        <button class="topcoat-button--cta" ng-click="addApp()">Add</button>
         <a href="#/"><button class="topcoat-button" ng-click="back()">Back</button></a>
     </div>
 </form>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/cdvah_views/list.html
----------------------------------------------------------------------
diff --git a/www/cdvah_views/list.html b/www/cdvah_views/list.html
index bc69941..e6b80e1 100644
--- a/www/cdvah_views/list.html
+++ b/www/cdvah_views/list.html
@@ -1,12 +1,12 @@
 <div class="topcoat-list__container">
     <h3 class="topcoat-list__header">Installed Apps</h3>
     <ul class="topcoat-list">
-        <li class="topcoat-list__item" ng-repeat="app in appsList">
-            <p>{{app.Name}}</p>
-            <p>Installed on: {{app.Installed}}</p>
-            <button ng-click="launchApp(app.Name)">Launch</button>
-            <button ng-click="updateApp(app.Name)" ng-disabled="app.Source==='serve'">Update</button>
-            <button ng-click="removeApp(app.Name)">Remove</button>
+        <li class="topcoat-list__item" ng-repeat="app in appList">
+            <p>{{app.appId}}</p>
+            <p>Last updated: {{app.lastUpdated || 'never'}}</p>
+            <button ng-click="launchApp(app)">Launch</button>
+            <button ng-click="updateApp(app)">Update</button>
+            <button ng-click="removeApp(app)">Remove</button>
         </li>
     </ul>
 </div>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/9ae368ee/www/config.xml
----------------------------------------------------------------------
diff --git a/www/config.xml b/www/config.xml
index 3ad17db..231c337 100644
--- a/www/config.xml
+++ b/www/config.xml
@@ -20,4 +20,5 @@
 <widget id="org.apache.appharness" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
     <name>CordovaAppHarness</name>
     <content src="cdvah_index.html" />
+    <access origin="*" />
 </widget>


Mime
View raw message