private void gridApps_MouseClick(object sender, MouseEventArgs e)
        {
            var hti             = gridApps.HitTest(e.X, e.Y);
            int currentRow      = hti.RowIndex;
            int currentCol      = hti.ColumnIndex;
            var plan            = ctrl.GetCurrentPlan();
            var planAppDefsDict = (plan != null) ? (from ad in plan.getAppDefs() select ad).ToDictionary(ad => ad.AppIdTuple, ad => ad) : new Dictionary <AppIdTuple, AppDef>();

            if (currentRow >= 0) // ignore header clicks
            {
                DataGridViewRow focused      = gridApps.Rows[currentRow];
                var             appIdTuple   = new AppIdTuple(focused.Cells[0].Value as string);
                var             st           = ctrl.GetAppState(appIdTuple);
                bool            connected    = callbacks.isConnectedDeleg();
                bool            isLocalApp   = appIdTuple.MachineId == this.machineId;
                bool            isAccessible = isLocalApp || connected; // can we change its state?
                var             appDef       = planAppDefsDict.ContainsKey(appIdTuple) ? planAppDefsDict[appIdTuple] : null;

                if (e.Button == MouseButtons.Right)
                {
                    // build popup menu
                    var popup = new System.Windows.Forms.ContextMenuStrip(this.components);
                    popup.Enabled = connected || allowLocalIfDisconnected;

                    var launchItem = new System.Windows.Forms.ToolStripMenuItem("&Launch");
                    launchItem.Click  += (s, a) => guardedOp(() => ctrl.LaunchApp(appIdTuple));
                    launchItem.Enabled = isAccessible && !st.Running;
                    popup.Items.Add(launchItem);

                    var killItem = new System.Windows.Forms.ToolStripMenuItem("&Kill");
                    killItem.Click  += (s, a) => guardedOp(() => ctrl.KillApp(appIdTuple));
                    killItem.Enabled = isAccessible && st.Running;
                    popup.Items.Add(killItem);

                    var restartItem = new System.Windows.Forms.ToolStripMenuItem("&Restart");
                    restartItem.Click  += (s, a) => guardedOp(() => ctrl.RestartApp(appIdTuple));
                    restartItem.Enabled = isAccessible && st.Running;
                    popup.Items.Add(restartItem);

                    if (appDef != null && appDef.Disabled)
                    {
                        var setEnabledItem = new System.Windows.Forms.ToolStripMenuItem("&Enable");
                        setEnabledItem.Click += (s, a) => guardedOp(() => ctrl.SetAppEnabled(plan.Name, appIdTuple, true));
                        popup.Items.Add(setEnabledItem);
                    }

                    if (appDef != null && !appDef.Disabled)
                    {
                        var setEnabledItem = new System.Windows.Forms.ToolStripMenuItem("&Disable");
                        setEnabledItem.Click += (s, a) => guardedOp(() => ctrl.SetAppEnabled(plan.Name, appIdTuple, false));
                        popup.Items.Add(setEnabledItem);
                    }


                    popup.Show(Cursor.Position);
                }
                else
                if (e.Button == MouseButtons.Left)
                {
                    // icon clicks
                    if (currentCol == appTabColIconStart)
                    {
                        if (isAccessible && !st.Running)
                        {
                            guardedOp(() => ctrl.LaunchApp(appIdTuple));
                        }
                    }

                    if (currentCol == appTabColIconKill)
                    {
                        if (isAccessible && st.Running)
                        {
                            guardedOp(() => ctrl.KillApp(appIdTuple));
                        }
                    }

                    if (currentCol == appTabColIconRestart)
                    {
                        if (isAccessible && st.Running)
                        {
                            guardedOp(() => ctrl.RestartApp(appIdTuple));
                        }
                    }

                    if (currentCol == appTabColEnabled)
                    {
                        var wasEnabled = (bool)focused.Cells[currentCol].Value;
                        if (plan != null)
                        {
                            guardedOp(() => ctrl.SetAppEnabled(plan.Name, appIdTuple, !wasEnabled));
                        }
                        else
                        {
                            //MessageBox.Show("Application is not part of selected plan. Select a different plan!", "Dirigent", MessageBoxButtons.OK, MessageBoxIcon.Information);
                        }
                    }
                }
            }
        }
        string getAppStatusCode(AppIdTuple appIdTuple, AppState st, bool isPartOfPlan)
        {
            string stCode = "Not running";

            bool connected   = callbacks.isConnectedDeleg();
            var  currTime    = DateTime.UtcNow;
            bool isRemoteApp = appIdTuple.MachineId != this.machineId;

            if (isRemoteApp && !connected)
            {
                stCode = "??? (discon.)";
                return(stCode);
            }

            var currPlan = ctrl.GetCurrentPlan();

            if (currPlan != null)
            {
                var  planState   = ctrl.GetPlanState(currPlan.Name);
                bool planRunning = (currPlan != null) && planState.Running && isPartOfPlan;
                if (planRunning && !st.PlanApplied && !st.Disabled)
                {
                    stCode = "Planned";
                }
            }

            if (st.Started)
            {
                if (st.Running && !st.Initialized)
                {
                    stCode = "Initializing";
                }
                if (st.Running && st.Initialized)
                {
                    stCode = "Running";
                }
                if (!st.Running)
                {
                    if (st.Killed)
                    {
                        stCode = "Killed";
                    }
                    else
                    {
                        stCode = string.Format("Terminated ({0})", st.ExitCode);
                    }
                }
            }
            else
            if (st.StartFailed)
            {
                stCode = "Failed to start";
            }

            var statusInfoAge = currTime - st.LastChange;

            if (isRemoteApp && statusInfoAge > TimeSpan.FromSeconds(3))
            {
                stCode += string.Format(" (Offline for {0:0} sec)", statusInfoAge.TotalSeconds);
            }


            return(stCode);
        }
Ejemplo n.º 3
0
 public SetAppEnabledMessage(string planName, AppIdTuple appIdTuple, bool enabled)
 {
     this.planName   = planName;
     this.appIdTuple = appIdTuple;
     this.enabled    = enabled;
 }
Ejemplo n.º 4
0
 public LaunchAppMessage(AppIdTuple appIdTuple)
 {
     this.appIdTuple = appIdTuple;
 }
Ejemplo n.º 5
0
 public KillAppMessage(AppIdTuple appIdTuple)
 {
     this.appIdTuple = appIdTuple;
 }
Ejemplo n.º 6
0
 public RestartAppMessage(AppIdTuple appIdTuple)
 {
     this.appIdTuple = appIdTuple;
 }
Ejemplo n.º 7
0
        private void gridApps_MouseClick(object sender, MouseEventArgs e)
        {
            var hti             = gridApps.HitTest(e.X, e.Y);
            int currentRow      = hti.RowIndex;
            int currentCol      = hti.ColumnIndex;
            var plan            = _currentPlan;
            var planAppDefsDict = (plan != null) ? (from ad in plan.AppDefs select ad).ToDictionary(ad => ad.Id, ad => ad) : new Dictionary <AppIdTuple, AppDef>();

            if (currentRow >= 0)              // ignore header clicks
            {
                DataGridViewRow focused   = gridApps.Rows[currentRow];
                var             id        = new AppIdTuple(focused.Cells[0].Value as string);
                var             st        = _ctrl.GetAppState(id);
                bool            connected = IsConnected;
                //bool isLocalApp = id.MachineId == this._machineId;
                bool isAccessible = connected;                 // can we change its state?
                var  appDef       = planAppDefsDict.ContainsKey(id) ? planAppDefsDict[id] : null;

                if (e.Button == MouseButtons.Right)
                {
                    // build popup menu
                    var popup = new System.Windows.Forms.ContextMenuStrip(this.components);
                    popup.Enabled = connected || _allowLocalIfDisconnected;

                    var launchItem = new System.Windows.Forms.ToolStripMenuItem("&Launch");
                    launchItem.Click += (s, a) => guardedOp(() => _ctrl.Send(new Net.StartAppMessage(
                                                                                 _ctrl.Name,
                                                                                 id,
                                                                                 Tools.IsAppInPlan(_ctrl, id, _currentPlan) ? _currentPlan.Name : null // prefer selected plan over others
                                                                                 )));
                    launchItem.Enabled = isAccessible && !st.Running;
                    popup.Items.Add(launchItem);

                    var killItem = new System.Windows.Forms.ToolStripMenuItem("&Kill");
                    killItem.Click  += (s, a) => guardedOp(() => _ctrl.Send(new Net.KillAppMessage(_ctrl.Name, id)));
                    killItem.Enabled = isAccessible && (st.Running || st.Restarting);
                    popup.Items.Add(killItem);

                    var restartItem = new System.Windows.Forms.ToolStripMenuItem("&Restart");
                    restartItem.Click  += (s, a) => guardedOp(() => _ctrl.Send(new Net.RestartAppMessage(_ctrl.Name, id)));
                    restartItem.Enabled = isAccessible;                     // && st.Running;
                    popup.Items.Add(restartItem);

                    if (appDef != null && appDef.Disabled)
                    {
                        var setEnabledItem = new System.Windows.Forms.ToolStripMenuItem("&Enable");
                        setEnabledItem.Click += (s, a) => guardedOp(() => _ctrl.Send(new Net.SetAppEnabledMessage(_ctrl.Name, plan.Name, id, true)));
                        popup.Items.Add(setEnabledItem);
                    }

                    if (appDef != null)
                    {
                        var setEnabledItem = new System.Windows.Forms.ToolStripMenuItem("&Disable");
                        setEnabledItem.Click += (s, a) => guardedOp(() => _ctrl.Send(new Net.SetAppEnabledMessage(_ctrl.Name, plan.Name, id, false)));
                        popup.Items.Add(setEnabledItem);
                    }

                    //var propsItem = new System.Windows.Forms.ToolStripMenuItem( "&Properties" );
                    //propsItem.Click += ( s, a ) => guardedOp( () =>
                    //{
                    //	try{
                    //	var appDef = planAppDefsDict.ContainsKey( id ) ? planAppDefsDict[id] : null;
                    //	var serializer = new System.Runtime.Serialization.DataContractSerializer(typeof(AppDef));
                    //	using var sw = new StringWriter();
                    //	using var writer = new System.Xml.XmlTextWriter(sw);
                    //	writer.Formatting = System.Xml.Formatting.Indented; // indent the Xml so it's human readable
                    //	serializer.WriteObject(writer, appDef);
                    //	writer.Flush();
                    //	var xmlString = sw.ToString();
                    //	MessageBox.Show(xmlString);
                    //	} catch( Exception ex )
                    //	{
                    //		int i =1;
                    //	}
                    //});
                    //propsItem.Enabled = true;
                    //popup.Items.Add( propsItem );



                    popup.Show(Cursor.Position);
                }
                else if (e.Button == MouseButtons.Left)
                {
                    // icon clicks
                    if (currentCol == appTabColIconStart)
                    {
                        if (isAccessible)                          // && !st.Running )
                        {
                            guardedOp(() => _ctrl.Send(new Net.StartAppMessage(
                                                           _ctrl.Name,
                                                           id,
                                                           Tools.IsAppInPlan(_ctrl, id, _currentPlan) ? _currentPlan.Name : null       // prefer selected plan over others
                                                           )));
                        }
                    }

                    if (currentCol == appTabColIconKill)
                    {
                        if (isAccessible)                          // && st.Running )
                        {
                            guardedOp(() => _ctrl.Send(new Net.KillAppMessage(_ctrl.Name, id)));
                        }
                    }

                    if (currentCol == appTabColIconRestart)
                    {
                        if (isAccessible)                          // && st.Running )
                        {
                            guardedOp(() => _ctrl.Send(new Net.RestartAppMessage(_ctrl.Name, id)));
                        }
                    }

                    if (currentCol == appTabColEnabled)
                    {
                        var wasEnabled = ( bool )focused.Cells[currentCol].Value;
                        if (plan != null)
                        {
                            guardedOp(() => _ctrl.Send(new Net.SetAppEnabledMessage(_ctrl.Name, plan.Name, id, !wasEnabled)));
                        }
                        else
                        {
                            //MessageBox.Show("Application is not part of selected plan. Select a different plan!", "Dirigent", MessageBoxButtons.OK, MessageBoxIcon.Information);
                        }
                    }
                }
            }
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Update the list of apps by doing minimal changes to avoid losing focus.
        /// Adding what is not yet there and deleting what has disappeared.
        /// </summary>
        void refreshAppList_smart()
        {
            DataGridViewRow selected = null;

            var plan = _currentPlan;

            var planAppDefsDict = (plan != null) ? (from ad in plan.AppDefs select ad).ToDictionary(ad => ad.Id, ad => ad) : new Dictionary <AppIdTuple, AppDef>();
            var planAppIdTuples = (plan != null) ? (from ad in plan.AppDefs select ad.Id).ToList() : new List <AppIdTuple>();

            Dictionary <AppIdTuple, AppState> appStates;

            if (ShowJustAppFromCurrentPlan)
            {
                appStates = (from i in _ctrl.GetAllAppStates() where planAppIdTuples.Contains(i.Key) select i).ToDictionary(mc => mc.Key, mc => mc.Value);
            }
            else             // show from all plans
            {
                appStates = new Dictionary <AppIdTuple, AppState>(_ctrl.GetAllAppStates());
            }

            // remember apps from plan
            Dictionary <string, AppIdTuple> newApps = new Dictionary <string, AppIdTuple>();

            foreach (AppIdTuple a in appStates.Keys)
            {
                newApps[a.ToString()] = a;
            }

            // remember apps from list
            Dictionary <string, DataGridViewRow> oldApps = new Dictionary <string, DataGridViewRow>();

            foreach (DataGridViewRow item in gridApps.Rows)
            {
                string id = item.Cells[appTabColName].Value as string;
                oldApps[id] = item;

                if (item.Selected)
                {
                    if (selected == null)
                    {
                        selected = item;
                    }
                }
            }

            // determine what to add and what to remove
            List <DataGridViewRow> toRemove = new List <DataGridViewRow>();
            List <object[]>        toAdd    = new List <object[]>();

            foreach (DataGridViewRow item in gridApps.Rows)
            {
                string id = item.Cells[0].Value as string;
                if (!newApps.ContainsKey(id))
                {
                    toRemove.Add(item);
                }
            }

            foreach (var x in appStates)
            {
                var idStr = x.Key.ToString();
                if (!oldApps.ContainsKey(idStr))
                {
                    var id       = x.Key;
                    var appState = x.Value;
                    var item     = new object[appTabNumCols];
                    item[appTabColName] = idStr;
                    //item[appTabColStatus] = getAppStatusCode( id, appState, planAppIdTuples.Contains( id ) );
                    item[appTabColStatus]      = Tools.GetAppStateText(appState, _ctrl.GetPlanState(appState.PlanName), _ctrl.GetAppDef(id));
                    item[appTabColIconStart]   = ResizeImage(new Bitmap(Resource1.play), new Size(20, 20));
                    item[appTabColIconKill]    = ResizeImage(new Bitmap(Resource1.delete), new Size(20, 20));
                    item[appTabColIconRestart] = ResizeImage(new Bitmap(Resource1.refresh), new Size(20, 20));
                    item[appTabColEnabled]     = false;
                    item[appTabColPlan]        = GetPlanForApp(id);
                    toAdd.Add(item);
                }
            }

            foreach (var i in toRemove)
            {
                gridApps.Rows.Remove(i);
            }

            foreach (var i in toAdd)
            {
                gridApps.Rows.Add(i);
            }

            Dictionary <DataGridViewRow, UPD> toUpdate = new Dictionary <DataGridViewRow, UPD>();

            foreach (var o in oldApps)
            {
                if (!toRemove.Contains(o.Value))
                {
                    var id       = newApps[o.Key];
                    var appState = _ctrl.GetAppState(id);
                    var upd      = new UPD()
                    {
                        //Status = getAppStatusCode( id, appState, planAppIdTuples.Contains( id ) ),
                        Status   = Tools.GetAppStateText(appState, _ctrl.GetPlanState(appState.PlanName), _ctrl.GetAppDef(id)),
                        PlanName = null
                    };
                    if (appState.PlanName != null)
                    {
                        upd.PlanName = appState.PlanName;
                    }
                    toUpdate[o.Value] = upd;
                }
            }

            foreach (var tu in toUpdate)
            {
                var row = tu.Key;
                var upd = tu.Value;

                row.Cells[appTabColStatus].Value = upd.Status;

                if (upd.PlanName != null)
                {
                    row.Cells[appTabColPlan].Value = upd.PlanName;
                }
            }

            // colorize the background of items from current plan
            List <string> planAppIds = (from ad in planAppIdTuples select ad.ToString()).ToList();

            foreach (DataGridViewRow item in gridApps.Rows)
            {
                string idStr = item.Cells[0].Value as string;
                var    id    = AppIdTuple.fromString(idStr, "");

                if (planAppIds.Contains(idStr))
                {
                    item.DefaultCellStyle.BackColor = Color.LightGoldenrodYellow;
                }
                else
                {
                    item.DefaultCellStyle.BackColor = SystemColors.Control;
                }

                // set checkbox based on Enabled attribute od the appDef from current plan
                var appDef = planAppDefsDict.ContainsKey(id) ? planAppDefsDict[id] : null;
                {
                    var chkCell = item.Cells[appTabColEnabled] as DataGridViewCheckBoxCell;
                    chkCell.Value = appDef != null ? !appDef.Disabled : false;
                    // emulate "Disabled" grayed appearance
                    chkCell.FlatStyle       = appDef != null ? FlatStyle.Standard : FlatStyle.Flat;
                    chkCell.Style.ForeColor = appDef != null ? Color.Black : Color.DarkGray;
                    chkCell.ReadOnly        = appDef == null;
                }
                // put app state into a tooltip
                {
                    var appStatusCell = item.Cells[appTabColStatus];                     // as DataGridViewCell;
                    appStatusCell.ToolTipText = Tools.GetAppStateString(id, _ctrl.GetAppState(id));
                }
            }
        }
 /// <summary>
 /// Sets new status info for given app.
 /// To be used for remote apps whose status gets received from master.
 /// </summary>
 /// <param name="appIdTuple"></param>
 /// <param name="appState"></param>
 public void SetRemoteAppState(AppIdTuple appIdTuple, AppState appState)
 {
     appsState[appIdTuple] = appState;
 }
Ejemplo n.º 10
0
        private void gridApps_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            var id    = new AppIdTuple(( string )gridApps.Rows[e.RowIndex].Cells[appTabColName].Value);
            var cell  = gridApps.Rows[e.RowIndex].Cells[e.ColumnIndex];
            var defst = gridApps.Rows[e.RowIndex].Cells[appTabColName].Style;

            if (e.ColumnIndex == appTabColStatus)
            {
                var txt = gridApps.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as string;
                if (txt.StartsWith("Running"))
                {
                    cell.Style = new DataGridViewCellStyle {
                        ForeColor = Color.DarkGreen, SelectionForeColor = Color.LightGreen, BackColor = defst.BackColor
                    };
                }
                else if (txt.StartsWith("Planned"))
                {
                    cell.Style = new DataGridViewCellStyle {
                        ForeColor = Color.DarkViolet, SelectionForeColor = Color.Violet, BackColor = defst.BackColor
                    };
                }
                else if (txt.StartsWith("Initializing"))
                {
                    cell.Style = new DataGridViewCellStyle {
                        ForeColor = Color.DarkOrange, SelectionForeColor = Color.Orange, BackColor = defst.BackColor
                    };
                }
                else if (txt.StartsWith("Terminated"))
                {
                    var appDef =
                        (from p in _ctrl.GetAllPlanDefs()
                         from a in p.AppDefs
                         where a.Id == id
                         select a).FirstOrDefault();
                    if (appDef != null)
                    {
                        if (!appDef.Volatile)                          // just non-volatile apps are not supposed to terminate on their own...
                        {
                            cell.Style = new DataGridViewCellStyle {
                                ForeColor = Color.Red, SelectionForeColor = Color.Red, BackColor = defst.BackColor
                            };
                        }
                        else
                        {
                            cell.Style = defst;
                        }
                    }
                    else
                    {
                        cell.Style = defst;
                    }
                }
                else if (txt.StartsWith("Restarting") || txt.StartsWith("Dying"))
                {
                    cell.Style = new DataGridViewCellStyle {
                        ForeColor = Color.Blue, SelectionForeColor = Color.Blue, BackColor = defst.BackColor
                    };
                }
                else
                {
                    cell.Style = defst;
                }
            }
        }
Ejemplo n.º 11
0
 public void  RestartApp(AppIdTuple appIdTuple)
 {
     KillApp(appIdTuple);
     LaunchApp(appIdTuple);
 }
Ejemplo n.º 12
0
        /// <summary>
        /// Launches a local app if not already running.
        /// </summary>
        /// <param name="appIdTuple"></param>
        public void  LaunchApp(AppIdTuple appIdTuple)
        {
            if (!(localApps.ContainsKey(appIdTuple)))
            {
                throw new NotALocalApp(appIdTuple, machineId);
            }

            var la       = localApps[appIdTuple];
            var appState = appsState[la.AppDef.AppIdTuple];

            // don't do anything if the app is already running
            if (la.launcher != null && la.launcher.Running)
            {
                return;
            }

            log.DebugFormat("Launching app {0}", la.AppDef.AppIdTuple);


            // launch the application
            appState.Started     = false;
            appState.StartFailed = false;
            appState.Killed      = false;

            la.watchers.Clear();

            la.launcher = launcherFactory.createLauncher(la.AppDef, rootForRelativePaths);

            try
            {
                la.launcher.Launch();
                appState.Started     = true;
                appState.Initialized = true; // a watcher can set it to false upon its creation if it works like an AppInitDetector

                //// instantiate app watchers
                //foreach( var watcherDef in la.AppDef.Watchers )
                //{
                //    var w = appWatcherFactory.create( la.AppDef, appsState[appIdTuple], la.launcher.ProcessId, watcherDef);
                //    la.watchers.Add( w );
                //}

                // instantiate init detector (also a watcher)
                {
                    // compatibility with InitialCondition="timeout 2.0" ... convert to XML definition <timeout>2.0</timeout>
                    {
                        if (!string.IsNullOrEmpty(la.AppDef.InitializedCondition))
                        {
                            string name;
                            string args;
                            AppInitializedDetectorFactory.ParseDefinitionString(la.AppDef.InitializedCondition, out name, out args);
                            string xmlString = string.Format("<{0}>{1}</{0}>", name, args);

                            var aid = appAppInitializedDetectorFactory.create(la.AppDef, appsState[appIdTuple], la.launcher.ProcessId, XElement.Parse(xmlString));
                            log.DebugFormat("Adding InitDetector {0}, pid {1}", aid.GetType().Name, la.launcher.ProcessId);
                            la.watchers.Add(aid);
                        }
                    }

                    foreach (var xml in la.AppDef.InitDetectors)
                    {
                        var aid = appAppInitializedDetectorFactory.create(la.AppDef, appsState[appIdTuple], la.launcher.ProcessId, XElement.Parse(xml));

                        log.DebugFormat("Adding InitDetector {0}, pid {1}", aid.GetType().Name, la.launcher.ProcessId);
                        la.watchers.Add(aid);
                    }
                }

                // instantiate window positioners
                foreach (var xml in la.AppDef.WindowPosXml)
                {
                    log.DebugFormat("Adding WindowsPositioner, pid {0}", la.launcher.ProcessId);
                    var wpo = new WindowPositioner(la.AppDef, appsState[appIdTuple], la.launcher.ProcessId, XElement.Parse(xml));
                    la.watchers.Add(wpo);
                }

                // instantiate autorestarter
                if (la.AppDef.RestartOnCrash)
                {
                    log.DebugFormat("Adding AutoRestarter, pid {0}", la.launcher.ProcessId);
                    var ar = new AutoRestarter(la.AppDef, appsState[appIdTuple], la.launcher.ProcessId, new XElement("Autorestart"));
                    la.watchers.Add(ar);
                }
            }
            catch (Exception ex)  // app launching failed
            {
                log.ErrorFormat("Exception: App \"{0}\"start failure {1}", la.AppDef.AppIdTuple, ex);

                appState.StartFailed = true;
                throw;
            }
        }
Ejemplo n.º 13
0
 public void makeInitialized(AppIdTuple id)
 {
     makeLaunched(id);
     appsState[id].Initialized = true;
 }
Ejemplo n.º 14
0
 public void makeLaunched(AppIdTuple id)
 {
     appsState[id].PlanApplied = true;
     appsState[id].Started     = true;
     appsState[id].Running     = true;
 }
Ejemplo n.º 15
0
        /// <summary>
        /// Builds the list of waves as the result of application interdependencies.
        ///
        /// The first wawe will contain apps that do not depend on any other app.
        /// The second wave will contain the apps that depend on those from the first wave.
        /// Etc. untill all apps are processed.
        /// </summary>
        public static List <AppWave> build(IEnumerable <AppDef> launchPlan)
        {
            // seznam zbyvajicich aplikaci
            // seznam uz pouzitych aplikaci (ktere uz byly vlozeny do vln)
            // Prochazim seznam zbylych aplikaci a pro kazdou hledam, zda vsechny jeji zavislosti uz se nachazi
            // v seznamu pouzitych aplikaci. Pokud ano, zkopiruju aplikaci do aktualni vlny. Pro projiti
            // vsech aplikaci pak vezmu aplikace z aktulne vytvorene vlny, smazu je ze zbyvajicich a vlozim do pouzitych.

            List <AppDef> remaining = (from t in launchPlan where !t.Disabled select t).ToList(); // those not yet moved to any of the waves
            List <AppDef> used      = new List <AppDef>();                                        // those already moved to some of waves

            // allow fast lookup of appdef by its name
            Dictionary <AppIdTuple, AppDef> dictApps = new Dictionary <AppIdTuple, AppDef>();

            foreach (var app in remaining)
            {
                dictApps[app.AppIdTuple] = app;
            }

            var waves = new List <AppWave>(); // the resulting list of waves

            // For each of the remaining apps check whether all its dependencias were already moved to some of prevoiusly
            // built waves; if so, add the app to the current wave and move it from remaining to used.
            while (remaining.Count > 0)
            {
                List <AppDef> currentWave = new List <AppDef>(); // the wave currently being built

                foreach (var app in remaining)
                {
                    bool allDepsSatisfied = true;

                    if (app.Dependencies != null)
                    {
                        foreach (var depName in app.Dependencies)
                        {
                            AppIdTuple depId = AppIdTuple.fromString(depName, app.AppIdTuple.MachineId);

                            if (!dictApps.ContainsKey(depId))
                            {
                                // throw exception "Unknown dependency"
                                throw new UnknownDependencyException(depName);
                            }

                            var dep = dictApps[depId];
                            if (!used.Contains(dep))
                            {
                                allDepsSatisfied = false;
                                break;
                            }
                        }
                    }
                    if (allDepsSatisfied)
                    {
                        currentWave.Add(app);
                    }
                }

                // if there are no app in current wave, there must be some circular dependency
                // as there is no app that does not depend on
                if (currentWave.Count == 0)
                {
                    // throw exception "Circular dependency somewhere"
                    throw new CircularDependencyException();
                }

                // move apps that were added to the current wave from remaining to used
                foreach (var app in currentWave)
                {
                    remaining.Remove(app);
                    used.Add(app);
                }

                // add current wave to the resulting list of wawes
                waves.Add(new AppWave(currentWave));
            }

            return(waves);
        }