From 54c13efe6ae1bcc00cb439782dd893bc4ecb9d59 Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Wed, 20 Jan 2016 14:40:12 -0800
Subject: [PATCH 1/6] add migration for stack manager content

---
 server-console/package.json              |   2 +-
 server-console/src/main.js               | 152 +++++++++++++++++++++--
 server-console/src/modules/hf-process.js |   5 +-
 3 files changed, 147 insertions(+), 12 deletions(-)

diff --git a/server-console/package.json b/server-console/package.json
index 3e74186122..44f7b3d05e 100644
--- a/server-console/package.json
+++ b/server-console/package.json
@@ -27,7 +27,7 @@
     "always-tail": "0.2.0",
     "cheerio": "^0.19.0",
     "extend": "^3.0.0",
-    "mkdirp": "^0.5.1",
+    "fs-extra": "^0.26.4",
     "node-notifier": "^4.4.0",
     "os-homedir": "^1.0.1",
     "request": "2.67.0",
diff --git a/server-console/src/main.js b/server-console/src/main.js
index e6eba4d605..594f5afddc 100644
--- a/server-console/src/main.js
+++ b/server-console/src/main.js
@@ -13,7 +13,7 @@ const shell = require('shell');
 const os = require('os');
 const childProcess = require('child_process');
 const path = require('path');
-const fs = require('fs');
+const fs = require('fs-extra');
 const Tail = require('always-tail');
 const http = require('http');
 const unzip = require('unzip');
@@ -72,7 +72,6 @@ const buildInfo = getBuildInfo();
 
 console.log("build info", buildInfo);
 
-
 function getRootHifiDataDirectory() {
     var organization = "High Fidelity";
     if (buildInfo.releaseType != "PRODUCTION") {
@@ -260,12 +259,6 @@ global.domainServer = null;
 global.acMonitor = null;
 global.userConfig = userConfig;
 
-const GO_HOME_INDEX = 2;
-const SERVER_LABEL_INDEX = 0;
-const RESTART_INDEX = 4;
-const STOP_INDEX = 5;
-const SETTINGS_INDEX = 6;
-
 var LogWindow = function(ac, ds) {
     this.ac = ac;
     this.ds = ds;
@@ -308,6 +301,132 @@ function goHomeClicked() {
     }
 }
 
+function stackManagerBasePath() {
+    var dataPath = 'High Fidelity/Stack Manager/resources';
+
+    if (process.platform == "win32") {
+        return path.resolve(osHomeDir(), 'AppData/Local', dataPath);
+    } else if (process.platform == "darwin") {
+        return path.resolve(osHomeDir(), 'Library/Application Support', dataPath);
+    } else {
+        return ""
+    }
+}
+
+function isStackManagerContentPresent() {
+    var modelsPath = path.resolve(stackManagerBasePath(), 'models.json.gz');
+
+    try {
+        var stats = fs.lstatSync(modelsPath);
+
+        if (stats.isFile()) {
+            console.log("Stack Manager entities file discovered at " + modelsPath)
+            // we found a content file
+            return true;
+        }
+    } catch (e) {
+        console.log("Stack Manager entities file not found at " + modelsPath);
+    }
+}
+
+function promptToMigrateContent() {
+    var idx = dialog.showMessageBox({
+        type: 'question',
+        buttons: ['Yes', 'No'],
+        title: 'Migrate Content',
+        message: 'Are you sure?\n\nThis will stop your home server and replace everything in your home with your content from Stack Manager.'
+    });
+
+    if (idx == 0) {
+        if (homeServer.state != ProcessGroupStates.STOPPED) {
+            homeServer.on('state-update', function(processGroup) {
+                if (processGroup.state == ProcessGroupStates.STOPPED) {
+                    performContentMigration();
+                }
+            });
+
+            homeServer.stop();
+
+        } else {
+            performContentMigration();
+        }
+    }
+}
+
+function performContentMigration() {
+    // check if there is a models file to migrate
+    var modelsPath = path.resolve(stackManagerBasePath(), 'models.json.gz');
+
+    try {
+        var stats = fs.lstatSync(modelsPath);
+    } catch (e) {
+        // no entities file
+        dialog.showMessageBox({
+            type: 'info',
+            buttons: ['OK'],
+            title: 'Models File Not Found',
+            message: 'There is no models file at ' + modelsPath + '\n\nStack Manager content migration can not proceed.'
+        });
+
+        return;
+    }
+
+    var copyError = null;
+
+    function showMigrationCompletionDialog(copyError) {
+        if (!copyError) {
+            // show message for successful migration
+            dialog.showMessageBox({
+                type: 'info',
+                buttons: ['OK'],
+                title: 'Migration Complete',
+                message: 'Your Stack Manager content has been migrated.\n\nYour home server will now be restarted.'
+            });
+        } else {
+            // show error message for copy fail
+            dialog.showMessageBox({
+                type: 'info',
+                buttons: ['OK'],
+                title: 'Migration Failed',
+                message: 'There was an error copying your Stack Manager content: ' + copyError + '\n\nPlease try again.'
+            });
+        }
+    }
+
+    // we have a models file, try and copy it
+    var newModelsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'entities/models.json.gz')
+    console.log("Copying Stack Manager entity file from " + modelsPath + " to " + newModelsPath);
+
+    fs.copy(modelsPath, newModelsPath, function(error){
+        if (!error) {
+            // check if there are any assets to copy
+            var oldAssetsPath = path.resolve(stackManagerBasePath(), 'assets');
+
+            fs.readdir(oldAssetsPath, function(error, data){
+                if (error) {
+                    showMigrationCompletionDialog(error);
+                } else if (data.length > 0) {
+
+                    // assume this means the directory is not empty
+                    // and that we should copy it
+                    var newAssetsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'assets');
+
+                    console.log("Copying Stack Manager assets from " + oldAssetsPath + " to " + newAssetsPath);
+
+                    // attempt to copy the assets folder, show correct dialog depending on success/failure
+                    fs.copy(oldAssetsPath, newAssetsPath, showMigrationCompletionDialog);
+                } else {
+                    showMigrationCompletionDialog(null);
+                }
+            });
+        } else {
+            showMigrationCompletionDialog(error);
+        }
+    });
+
+    homeServer.start();
+}
+
 var logWindow = null;
 
 function buildMenuArray(serverState) {
@@ -372,12 +491,29 @@ function buildMenuArray(serverState) {
             }
         ];
 
+        var foundStackManagerContent = isStackManagerContentPresent();
+        if (foundStackManagerContent) {
+            // add a separator and the stack manager content migration option
+            menuArray.splice(menuArray.length - 1, 0, {
+                label: 'Migrate Stack Manager Content',
+                click: function() { promptToMigrateContent(); }
+            }, {
+                type: 'separator'
+            });
+        }
+
         updateMenuArray(menuArray, serverState);
     }
 
     return menuArray;
 }
 
+const GO_HOME_INDEX = 2;
+const SERVER_LABEL_INDEX = 0;
+const RESTART_INDEX = 4;
+const STOP_INDEX = 5;
+const SETTINGS_INDEX = 6;
+
 function updateMenuArray(menuArray, serverState) {
     // update the tray menu state
     var running = serverState == ProcessGroupStates.STARTED;
diff --git a/server-console/src/modules/hf-process.js b/server-console/src/modules/hf-process.js
index 5192e44bb6..c7f21ebbe5 100755
--- a/server-console/src/modules/hf-process.js
+++ b/server-console/src/modules/hf-process.js
@@ -5,8 +5,7 @@ const extend = require('extend');
 const util = require('util');
 const events = require('events');
 const childProcess = require('child_process');
-const fs = require('fs');
-const mkdirp = require('mkdirp');
+const fs = require('fs-extra');
 const os = require('os');
 const path = require('path');
 
@@ -133,7 +132,7 @@ Process.prototype = extend(Process.prototype, {
             var logDirectoryCreated = false;
 
             try {
-                mkdirp.sync(this.logDirectory);
+                fs.mkdirsSync(this.logDirectory);
                 logDirectoryCreated = true;
             } catch (e) {
                 if (e.code == 'EEXIST') {

From 278ab0fa8f5bb52ffee9bd2f86c83a7a96129f3c Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Wed, 20 Jan 2016 14:54:52 -0800
Subject: [PATCH 2/6] don't make the dialogs synchronous

---
 server-console/src/main.js | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/server-console/src/main.js b/server-console/src/main.js
index 594f5afddc..d360dbd32c 100644
--- a/server-console/src/main.js
+++ b/server-console/src/main.js
@@ -330,27 +330,27 @@ function isStackManagerContentPresent() {
 }
 
 function promptToMigrateContent() {
-    var idx = dialog.showMessageBox({
+    dialog.showMessageBox({
         type: 'question',
         buttons: ['Yes', 'No'],
         title: 'Migrate Content',
         message: 'Are you sure?\n\nThis will stop your home server and replace everything in your home with your content from Stack Manager.'
-    });
+    }, function(index) {
+        if (index == 0) {
+            if (homeServer.state != ProcessGroupStates.STOPPED) {
+                homeServer.on('state-update', function(processGroup) {
+                    if (processGroup.state == ProcessGroupStates.STOPPED) {
+                        performContentMigration();
+                    }
+                });
 
-    if (idx == 0) {
-        if (homeServer.state != ProcessGroupStates.STOPPED) {
-            homeServer.on('state-update', function(processGroup) {
-                if (processGroup.state == ProcessGroupStates.STOPPED) {
-                    performContentMigration();
-                }
-            });
+                homeServer.stop();
 
-            homeServer.stop();
-
-        } else {
-            performContentMigration();
+            } else {
+                performContentMigration();
+            }
         }
-    }
+    });
 }
 
 function performContentMigration() {
@@ -366,7 +366,7 @@ function performContentMigration() {
             buttons: ['OK'],
             title: 'Models File Not Found',
             message: 'There is no models file at ' + modelsPath + '\n\nStack Manager content migration can not proceed.'
-        });
+        }, null);
 
         return;
     }
@@ -381,7 +381,7 @@ function performContentMigration() {
                 buttons: ['OK'],
                 title: 'Migration Complete',
                 message: 'Your Stack Manager content has been migrated.\n\nYour home server will now be restarted.'
-            });
+            }, null);
         } else {
             // show error message for copy fail
             dialog.showMessageBox({
@@ -389,7 +389,7 @@ function performContentMigration() {
                 buttons: ['OK'],
                 title: 'Migration Failed',
                 message: 'There was an error copying your Stack Manager content: ' + copyError + '\n\nPlease try again.'
-            });
+            }, null);
         }
     }
 

From 430d1351db214a7ee22624f24a9a8f8f5f6be9ad Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Wed, 20 Jan 2016 14:58:15 -0800
Subject: [PATCH 3/6] preserve timestamps for assets copy

---
 server-console/src/main.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/server-console/src/main.js b/server-console/src/main.js
index d360dbd32c..28a69527a1 100644
--- a/server-console/src/main.js
+++ b/server-console/src/main.js
@@ -414,7 +414,9 @@ function performContentMigration() {
                     console.log("Copying Stack Manager assets from " + oldAssetsPath + " to " + newAssetsPath);
 
                     // attempt to copy the assets folder, show correct dialog depending on success/failure
-                    fs.copy(oldAssetsPath, newAssetsPath, showMigrationCompletionDialog);
+                    fs.copy(oldAssetsPath, newAssetsPath, {
+                        preserveTimestamps: true
+                    }, showMigrationCompletionDialog);
                 } else {
                     showMigrationCompletionDialog(null);
                 }

From 6ec55dc74602f9ac21b82c3c0ddc09ba807ff768 Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Wed, 20 Jan 2016 15:06:46 -0800
Subject: [PATCH 4/6] only wait for state change for migration once

---
 server-console/src/main.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server-console/src/main.js b/server-console/src/main.js
index 28a69527a1..52c56df5e3 100644
--- a/server-console/src/main.js
+++ b/server-console/src/main.js
@@ -338,7 +338,7 @@ function promptToMigrateContent() {
     }, function(index) {
         if (index == 0) {
             if (homeServer.state != ProcessGroupStates.STOPPED) {
-                homeServer.on('state-update', function(processGroup) {
+                homeServer.once('state-update', function(processGroup) {
                     if (processGroup.state == ProcessGroupStates.STOPPED) {
                         performContentMigration();
                     }

From d08a64a4d608ac7d37b10da7e54bdc257238c45b Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Wed, 20 Jan 2016 15:25:58 -0800
Subject: [PATCH 5/6] use a big try-catch for migration block

---
 server-console/src/main.js | 55 +++++++++++++++++++++-----------------
 1 file changed, 30 insertions(+), 25 deletions(-)

diff --git a/server-console/src/main.js b/server-console/src/main.js
index 52c56df5e3..e13d99b455 100644
--- a/server-console/src/main.js
+++ b/server-console/src/main.js
@@ -338,11 +338,17 @@ function promptToMigrateContent() {
     }, function(index) {
         if (index == 0) {
             if (homeServer.state != ProcessGroupStates.STOPPED) {
-                homeServer.once('state-update', function(processGroup) {
-                    if (processGroup.state == ProcessGroupStates.STOPPED) {
+                var stopThenMigrateCallback = function(processGroup) {
+                    if (isShuttingDown) {
+                        homeServer.removeListener('state-update', stopThenMigrateCallback);
+                    } else if (processGroup.state == ProcessGroupStates.STOPPED) {
                         performContentMigration();
+
+                        homeServer.removeListener('state-update', stopThenMigrateCallback);
                     }
-                });
+                };
+
+                homeServer.on('state-update', stopThenMigrateCallback);
 
                 homeServer.stop();
 
@@ -397,34 +403,33 @@ function performContentMigration() {
     var newModelsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'entities/models.json.gz')
     console.log("Copying Stack Manager entity file from " + modelsPath + " to " + newModelsPath);
 
-    fs.copy(modelsPath, newModelsPath, function(error){
-        if (!error) {
-            // check if there are any assets to copy
-            var oldAssetsPath = path.resolve(stackManagerBasePath(), 'assets');
+    var entitiesCopied = false;
 
-            fs.readdir(oldAssetsPath, function(error, data){
-                if (error) {
-                    showMigrationCompletionDialog(error);
-                } else if (data.length > 0) {
+    try {
+        fs.copySync(modelsPath, newModelsPath);
 
-                    // assume this means the directory is not empty
-                    // and that we should copy it
-                    var newAssetsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'assets');
+        // check if there are any assets to copy
+        var oldAssetsPath = path.resolve(stackManagerBasePath(), 'assets');
 
-                    console.log("Copying Stack Manager assets from " + oldAssetsPath + " to " + newAssetsPath);
+        var assets = fs.readdirSync(oldAssetsPath);
 
-                    // attempt to copy the assets folder, show correct dialog depending on success/failure
-                    fs.copy(oldAssetsPath, newAssetsPath, {
-                        preserveTimestamps: true
-                    }, showMigrationCompletionDialog);
-                } else {
-                    showMigrationCompletionDialog(null);
-                }
+        if (assets.length > 0) {
+            // assume this means the directory is not empty
+            // and that we should copy it
+            var newAssetsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'assets');
+
+            console.log("Copying Stack Manager assets from " + oldAssetsPath + " to " + newAssetsPath);
+
+            // attempt to copy the assets folder, show correct dialog depending on success/failure
+            fs.copySync(oldAssetsPath, newAssetsPath, {
+                preserveTimestamps: true
             });
-        } else {
-            showMigrationCompletionDialog(error);
         }
-    });
+
+        showMigrationCompletionDialog(null);
+    } catch (error) {
+        showMigrationCompletionDialog(error);
+    }
 
     homeServer.start();
 }

From f872dfac0420a6acc09a2fde5eb7bbf5693940ed Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Wed, 20 Jan 2016 15:31:23 -0800
Subject: [PATCH 6/6] remove some unused variables

---
 server-console/src/main.js | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/server-console/src/main.js b/server-console/src/main.js
index e13d99b455..a32e9328ff 100644
--- a/server-console/src/main.js
+++ b/server-console/src/main.js
@@ -349,7 +349,6 @@ function promptToMigrateContent() {
                 };
 
                 homeServer.on('state-update', stopThenMigrateCallback);
-
                 homeServer.stop();
 
             } else {
@@ -377,8 +376,6 @@ function performContentMigration() {
         return;
     }
 
-    var copyError = null;
-
     function showMigrationCompletionDialog(copyError) {
         if (!copyError) {
             // show message for successful migration
@@ -403,8 +400,6 @@ function performContentMigration() {
     var newModelsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'entities/models.json.gz')
     console.log("Copying Stack Manager entity file from " + modelsPath + " to " + newModelsPath);
 
-    var entitiesCopied = false;
-
     try {
         fs.copySync(modelsPath, newModelsPath);
 
@@ -420,7 +415,7 @@ function performContentMigration() {
 
             console.log("Copying Stack Manager assets from " + oldAssetsPath + " to " + newAssetsPath);
 
-            // attempt to copy the assets folder, show correct dialog depending on success/failure
+            // attempt to copy the assets folder
             fs.copySync(oldAssetsPath, newAssetsPath, {
                 preserveTimestamps: true
             });