diff --git a/Action/Rs232Command.cs b/Action/Rs232Command.cs index 7b17912f..12db207c 100644 --- a/Action/Rs232Command.cs +++ b/Action/Rs232Command.cs @@ -21,6 +21,7 @@ public Rs232Command(Command command) /// /// Run the command + /// throws an exception if we cannot open or write to the port /// public string Run() { @@ -29,6 +30,8 @@ public string Run() // Parse and configure the port parse(); + Trace.WriteLine(new LogMessage("Rs232Command - run", "Parsed command, will open port " + _port.PortName + " and write " + _toSend), LogType.Audit.ToString()); + // try to open the COM port if (!_port.IsOpen) _port.Open(); diff --git a/Action/XmrSubscriber.cs b/Action/XmrSubscriber.cs index f0e414ac..463f604d 100644 --- a/Action/XmrSubscriber.cs +++ b/Action/XmrSubscriber.cs @@ -175,7 +175,7 @@ public void Run() } catch (Exception e) { - Trace.WriteLine(new LogMessage("XmrSubscriber - Run", "Unable to Subscribe to XMR: " + e.Message), LogType.Error.ToString()); + Trace.WriteLine(new LogMessage("XmrSubscriber - Run", "Unable to Subscribe to XMR: " + e.Message), LogType.Info.ToString()); _clientInfoForm.XmrSubscriberStatus = e.Message; } } diff --git a/Control/EmbeddedServer.cs b/Control/EmbeddedServer.cs index 45e6fe70..561dd279 100644 --- a/Control/EmbeddedServer.cs +++ b/Control/EmbeddedServer.cs @@ -52,7 +52,14 @@ public void Run() using (WebServer server = new WebServer(ApplicationSettings.Default.EmbeddedServerAddress)) { - server.RegisterModule(new StaticFilesModule(ApplicationSettings.Default.LibraryPath)); + Dictionary headers = new Dictionary() + { + { Constants.HeaderCacheControl, "no-cache, no-store, must-revalidate" }, + { Constants.HeaderPragma, "no-cache" }, + { Constants.HeaderExpires, "0" } + }; + + server.RegisterModule(new StaticFilesModule(ApplicationSettings.Default.LibraryPath, headers)); server.Module().UseRamCache = true; server.Module().DefaultExtension = ".html"; diff --git a/Control/Region.cs b/Control/Region.cs index 61e9bf4e..76e59443 100644 --- a/Control/Region.cs +++ b/Control/Region.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2006-2015 Daniel Garner + * Copyright (C) 2006-2016 Daniel Garner * * This file is part of Xibo. * @@ -171,18 +171,26 @@ private void EvalOptions() if (!SetNextMediaNodeInOptions()) { // For some reason we cannot set a media node... so we need this region to become invalid - _hasExpired = true; - - if (DurationElapsedEvent != null) - DurationElapsedEvent(); - return; + throw new InvalidOperationException("Unable to set any region media nodes."); } // If the sequence hasnt been changed, OR the layout has been expired // there has been no change to the sequence, therefore the media we have already created is still valid // or this media has actually been destroyed and we are working out way out the call stack - if (_layoutExpired || (_currentSequence == temp)) + if (_layoutExpired) + { return; + } + else if (_currentSequence == temp) + { + // Media has not changed, we are likely the only valid media item in the region + // the layout has not yet expired, so depending on whether we loop or not, we either + // reload the same media item again + // or do nothing (return) + // This could be made more succinct, but is clearer written as an elseif. + if (!_options.RegionLoop) + return; + } // Store the Current Index _options.CurrentIndex = _currentSequence; @@ -199,7 +207,7 @@ private void EvalOptions() // Try the next node startSuccessful = false; continue; - } + } // First thing we do is stop the current stat record if (!initialMedia) @@ -449,23 +457,26 @@ private void ParseOptionsForMediaNode(XmlNode mediaNode, XmlAttributeCollection // And some stuff on Raw nodes XmlNode rawNode = mediaNode.SelectSingleNode("raw"); - foreach (XmlNode raw in rawNode.ChildNodes) + if (rawNode != null) { - if (raw.Name == "text") - { - _options.text = raw.InnerText; - } - else if (raw.Name == "template") - { - _options.documentTemplate = raw.InnerText; - } - else if (raw.Name == "embedHtml") + foreach (XmlNode raw in rawNode.ChildNodes) { - _options.text = raw.InnerText; - } - else if (raw.Name == "embedScript") - { - _options.javaScript = raw.InnerText; + if (raw.Name == "text") + { + _options.text = raw.InnerText; + } + else if (raw.Name == "template") + { + _options.documentTemplate = raw.InnerText; + } + else if (raw.Name == "embedHtml") + { + _options.text = raw.InnerText; + } + else if (raw.Name == "embedScript") + { + _options.javaScript = raw.InnerText; + } } } @@ -539,6 +550,8 @@ private Media CreateNextMediaNode(RegionOptions options) } else { + // We've set our next media node in options already + // this includes checking that file based media is valid. switch (options.type) { case "image": @@ -775,11 +788,16 @@ private void media_DurationElapsedEvent(int filesPlayed) /// /// Clears the Region of anything that it shouldnt still have... + /// called when Destroying a Layout and when Removing an Overlay /// public void Clear() { try { + // Stop the current media item + if (_media != null) + StopMedia(_media); + // What happens if we are disposing this region but we have not yet completed the stat event? if (string.IsNullOrEmpty(_stat.toDate)) { diff --git a/Control/WatchDogManager.cs b/Control/WatchDogManager.cs index c6dbb283..537424ac 100644 --- a/Control/WatchDogManager.cs +++ b/Control/WatchDogManager.cs @@ -13,6 +13,8 @@ class WatchDogManager public static void Start() { // Check to see if the WatchDog EXE exists where we expect it to be + // Uncomment to test local watchdog install. + //string path = @"C:\Program Files (x86)\Xibo Player\watchdog\x86\XiboClientWatchdog.exe"; string path = Path.GetDirectoryName(Application.ExecutablePath) + @"\watchdog\x86\XiboClientWatchdog.exe"; string args = "-p \"" + Application.ExecutablePath + "\" -l \"" + ApplicationSettings.Default.LibraryPath + "\""; @@ -21,7 +23,16 @@ public static void Start() { try { - Process.Start(path, args); + Process process = new Process(); + ProcessStartInfo info = new ProcessStartInfo(); + + info.CreateNoWindow = true; + info.WindowStyle = ProcessWindowStyle.Hidden; + info.FileName = "cmd.exe"; + info.Arguments = "/c start \"watchdog\" \"" + path + "\" " + args; + + process.StartInfo = info; + process.Start(); } catch (Exception e) { diff --git a/Error/DefaultLayoutException.cs b/Error/DefaultLayoutException.cs new file mode 100644 index 00000000..ac196cba --- /dev/null +++ b/Error/DefaultLayoutException.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace XiboClient.Error +{ + class DefaultLayoutException : Exception + { + } +} diff --git a/Log/ClientInfo.cs b/Log/ClientInfo.cs index f7d2536d..34bb646d 100644 --- a/Log/ClientInfo.cs +++ b/Log/ClientInfo.cs @@ -17,6 +17,9 @@ public partial class ClientInfo : Form public delegate void StatusDelegate(string status); public delegate void AddLogMessage(string message, LogType logType); + // Delegate for updating the status file + public delegate void UpdateStatusFile(); + /// /// Set the schedule status /// @@ -298,5 +301,29 @@ private void saveFileDialog_FileOk(object sender, CancelEventArgs e) MessageBox.Show("Log saved as " + saveFileDialog.FileName, "Log Saved"); } + + /// + /// Update Status Marker File + /// + public void UpdateStatusMarkerFile() + { + if (InvokeRequired) + { + BeginInvoke(new UpdateStatusFile(updateStatusFile)); + } + else + { + updateStatusFile(); + } + } + + /// + /// Update status file + /// + private void updateStatusFile() + { + File.WriteAllText(Path.Combine(ApplicationSettings.Default.LibraryPath, "status.json"), + "{\"lastActivity\":\"" + DateTime.Now.ToString() + "\",\"state\":\"" + Thread.State.ToString() + "\",\"xmdsLastActivity\":\"" + ApplicationSettings.Default.XmdsLastConnection.ToString() + "\",\"xmdsCollectInterval\":\"" + ApplicationSettings.Default.CollectInterval.ToString() + "\"}"); + } } } \ No newline at end of file diff --git a/Logic/ApplicationSettings.cs b/Logic/ApplicationSettings.cs index deeef9a6..ebbcac9a 100644 --- a/Logic/ApplicationSettings.cs +++ b/Logic/ApplicationSettings.cs @@ -39,9 +39,9 @@ public class ApplicationSettings private static string _default = "default"; // Application Specific Settings we want to protect - private string _clientVersion = "1.8.0-beta"; + private string _clientVersion = "1.8.0-rc1"; private string _version = "5"; - private int _clientCodeVersion = 122; + private int _clientCodeVersion = 124; public string ClientVersion { get { return _clientVersion; } } public string Version { get { return _version; } } diff --git a/Logic/CacheManager.cs b/Logic/CacheManager.cs index 53e0d3f2..15b97d56 100644 --- a/Logic/CacheManager.cs +++ b/Logic/CacheManager.cs @@ -199,7 +199,7 @@ public bool IsValidPath(String path) { // If we cached it over 2 minutes ago, then check the GetLastWriteTime if (file.cacheDate > DateTime.Now.AddMinutes(-2)) - return true; + return File.Exists(ApplicationSettings.Default.LibraryPath + @"\" + path); try { @@ -231,93 +231,6 @@ public bool IsValidPath(String path) } } - /// - /// Is the provided layout file a valid layout (has all media) - /// - /// - /// - public bool IsValidLayout(string layoutFile) - { - lock (_locker) - { - Debug.WriteLine("Checking if Layout " + layoutFile + " is valid"); - - if (!IsValidPath(layoutFile)) - return false; - - - // Load the XLF, get all media ID's - XmlDocument layoutXml = new XmlDocument(); - layoutXml.Load(ApplicationSettings.Default.LibraryPath + @"\" + layoutFile); - - try - { - XmlNodeList mediaNodes = layoutXml.SelectNodes("//media"); - - // Store some information about the validity of local video to decide if this layout should be valid or not. - int countInvalidLocalVideo = 0; - - foreach (XmlNode media in mediaNodes) - { - // Is this a stored media type? - switch (media.Attributes["type"].Value) - { - case "video": - case "image": - case "flash": - case "powerpoint": - - // Get the path and see if its - if (!IsValidPath(GetUri(media))) - { - Trace.WriteLine(new LogMessage("CacheManager - IsValidLayout", "Invalid Media: " + media.Attributes["id"].Value.ToString()), LogType.Audit.ToString()); - return false; - } - - break; - - default: - continue; - } - } - - // If the number of invalid local video elements is equal to the number of elements on the layout, then don't show - if (countInvalidLocalVideo == mediaNodes.Count) - return false; - } - catch (Exception ex) - { - Trace.WriteLine(new LogMessage("CacheManager - IsValidLayout", "Exception checking media. " + ex.Message), LogType.Audit.ToString()); - return false; - } - - // Also check to see if there is a background image that needs to be downloaded - try - { - XmlNode layoutNode = layoutXml.SelectSingleNode("/layout"); - XmlAttributeCollection layoutAttributes = layoutNode.Attributes; - - if (layoutAttributes["background"] != null && !string.IsNullOrEmpty(layoutAttributes["background"].Value)) - { - if (!IsValidPath(layoutAttributes["background"].Value)) - { - Debug.WriteLine("Invalid background: " + layoutAttributes["background"].Value); - return false; - } - } - } - catch - { - // We dont want a missing background attribute to stop this process - return true; - } - - Debug.WriteLine("Layout " + layoutFile + " is valid"); - - return true; - } - } - /// /// Get the URI of this media item /// diff --git a/Logic/HardwareKey.cs b/Logic/HardwareKey.cs index 5ca7cc54..6b3444e5 100644 --- a/Logic/HardwareKey.cs +++ b/Logic/HardwareKey.cs @@ -72,6 +72,9 @@ public HardwareKey() { Debug.WriteLine("[IN]", "HardwareKey"); + // Get the Mac Address + _macAddress = GetMacAddress(); + // Get the key from the Settings _hardwareKey = ApplicationSettings.Default.HardwareKey; @@ -80,8 +83,10 @@ public HardwareKey() { try { + string systemDriveLetter = Path.GetPathRoot(Environment.SystemDirectory); + // Calculate the Hardware key from the CPUID and Volume Serial - _hardwareKey = Hashes.MD5(GetCPUId() + GetVolumeSerial("C")); + _hardwareKey = Hashes.MD5(GetCPUId() + GetVolumeSerial(systemDriveLetter[0].ToString()) + _macAddress); } catch { @@ -92,9 +97,6 @@ public HardwareKey() ApplicationSettings.Default.HardwareKey = _hardwareKey; } - // Get the Mac Address - _macAddress = GetMacAddress(); - Debug.WriteLine("[OUT]", "HardwareKey"); } @@ -154,14 +156,21 @@ private string GetMacAddress() { string macAddresses = string.Empty; - foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) + try { - if (nic.OperationalStatus == OperationalStatus.Up) + foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) { - macAddresses += BitConverter.ToString(nic.GetPhysicalAddress().GetAddressBytes()).Replace('-', ':'); - break; + if (nic.OperationalStatus == OperationalStatus.Up) + { + macAddresses += BitConverter.ToString(nic.GetPhysicalAddress().GetAddressBytes()).Replace('-', ':'); + break; + } } } + catch + { + macAddresses = "00:00:00:00:00:00"; + } return macAddresses; } @@ -203,7 +212,7 @@ public AsymmetricCipherKeyPair getXmrKey() const int PROVIDER_RSA_FULL = 1; CspParameters cspParams; cspParams = new CspParameters(PROVIDER_RSA_FULL); - cspParams.KeyContainerName = Application.ProductName + "RsaKey"; + cspParams.KeyContainerName = Application.ProductName + "-" + Environment.UserName + "-" + "RsaKey"; cspParams.Flags = CspProviderFlags.UseMachineKeyStore; cspParams.ProviderName = "Microsoft Strong Cryptographic Provider"; @@ -217,15 +226,23 @@ public AsymmetricCipherKeyPair getXmrKey() public string getXmrPublicKey() { - AsymmetricCipherKeyPair key = getXmrKey(); - - using (TextWriter textWriter = new StringWriter()) + try { - PemWriter writer = new PemWriter(textWriter); - writer.WriteObject(key.Public); - writer.Writer.Flush(); + AsymmetricCipherKeyPair key = getXmrKey(); + + using (TextWriter textWriter = new StringWriter()) + { + PemWriter writer = new PemWriter(textWriter); + writer.WriteObject(key.Public); + writer.Writer.Flush(); - return textWriter.ToString(); + return textWriter.ToString(); + } + } + catch (Exception e) + { + Trace.WriteLine(new LogMessage("HardwareKey - getXmrPublicKey", "Unable to get XMR public key. E = " + e.Message), LogType.Error.ToString()); + return null; } } } diff --git a/Logic/RegionOptions.cs b/Logic/RegionOptions.cs index 6013d986..583dd67e 100644 --- a/Logic/RegionOptions.cs +++ b/Logic/RegionOptions.cs @@ -46,6 +46,9 @@ struct RegionOptions public string uri; public int duration; + // Region Loop + public bool RegionLoop; + //xml public XmlNodeList mediaNodes; diff --git a/Logic/Schedule.cs b/Logic/Schedule.cs index d085a4f4..064cba23 100644 --- a/Logic/Schedule.cs +++ b/Logic/Schedule.cs @@ -64,6 +64,25 @@ class Schedule private string _scheduleLocation; + /// + /// The current layout id + /// + public int CurrentLayoutId + { + get + { + return _currentLayoutId; + } + set + { + _currentLayoutId = value; + + if (_scheduleManager != null) + _scheduleManager.CurrentLayoutId = _currentLayoutId; + } + } + private int _currentLayoutId; + private bool _forceChange = false; /// @@ -85,13 +104,9 @@ class Schedule private RegisterAgent _registerAgent; Thread _registerAgentThread; - // Schedule Agent - private ScheduleAgent _scheduleAgent; - Thread _scheduleAgentThread; - // Required Files Agent - private RequiredFilesAgent _requiredFilesAgent; - Thread _requiredFilesAgentThread; + private ScheduleAndFilesAgent _scheduleAndRfAgent; + Thread _scheduleAndRfAgentThread; // Library Agent private LibraryAgent _libraryAgent; @@ -154,28 +169,19 @@ public Schedule(string scheduleLocation, ref CacheManager cacheManager, ref Clie _scheduleManagerThread = new Thread(new ThreadStart(_scheduleManager.Run)); _scheduleManagerThread.Name = "ScheduleManagerThread"; - // Create a Schedule Agent - _scheduleAgent = new ScheduleAgent(); - _scheduleAgent.CurrentScheduleManager = _scheduleManager; - _scheduleAgent.ScheduleLocation = scheduleLocation; - _scheduleAgent.HardwareKey = _hardwareKey.Key; - _scheduleAgent.ClientInfoForm = _clientInfoForm; - - // Create a thread for the Schedule Agent to run in - but dont start it up yet. - _scheduleAgentThread = new Thread(new ThreadStart(_scheduleAgent.Run)); - _scheduleAgentThread.Name = "ScheduleAgentThread"; - // Create a RequiredFilesAgent - _requiredFilesAgent = new RequiredFilesAgent(); - _requiredFilesAgent.CurrentCacheManager = cacheManager; - _requiredFilesAgent.HardwareKey = _hardwareKey.Key; - _requiredFilesAgent.ClientInfoForm = _clientInfoForm; - _requiredFilesAgent.OnComplete += new RequiredFilesAgent.OnCompleteDelegate(LayoutFileModified); - _requiredFilesAgent.OnFullyProvisioned += _requiredFilesAgent_OnFullyProvisioned; + _scheduleAndRfAgent = new ScheduleAndFilesAgent(); + _scheduleAndRfAgent.CurrentCacheManager = cacheManager; + _scheduleAndRfAgent.CurrentScheduleManager = _scheduleManager; + _scheduleAndRfAgent.ScheduleLocation = scheduleLocation; + _scheduleAndRfAgent.HardwareKey = _hardwareKey.Key; + _scheduleAndRfAgent.OnFullyProvisioned += _requiredFilesAgent_OnFullyProvisioned; + _scheduleAndRfAgent.ClientInfoForm = _clientInfoForm; + _scheduleAndRfAgent.OnComplete += new ScheduleAndFilesAgent.OnCompleteDelegate(LayoutFileModified); // Create a thread for the RequiredFiles Agent to run in - but dont start it up yet. - _requiredFilesAgentThread = new Thread(new ThreadStart(_requiredFilesAgent.Run)); - _requiredFilesAgentThread.Name = "RequiredFilesAgentThread"; + _scheduleAndRfAgentThread = new Thread(new ThreadStart(_scheduleAndRfAgent.Run)); + _scheduleAndRfAgentThread.Name = "RequiredFilesAgentThread"; // Library Agent _libraryAgent = new LibraryAgent(); @@ -216,11 +222,8 @@ public void InitializeComponents() // Start the RegisterAgent thread _registerAgentThread.Start(); - // Start the ScheduleAgent thread - _scheduleAgentThread.Start(); - // Start the RequiredFilesAgent thread - _requiredFilesAgentThread.Start(); + _scheduleAndRfAgentThread.Start(); // Start the ScheduleManager thread _scheduleManagerThread.Start(); @@ -270,13 +273,29 @@ void _scheduleManager_OnRefreshSchedule() /// void _scheduleManager_OnScheduleManagerCheckComplete() { + if (agentThreadsAlive()) + { + // Update status marker on the main thread. + _clientInfoForm.UpdateStatusMarkerFile(); + } + else + { + Trace.WriteLine(new LogMessage("Schedule - OnScheduleManagerCheckComplete", "Agent threads are dead, not updating status.json"), LogType.Error.ToString()); + } + try { // See if XMR should be running if (!string.IsNullOrEmpty(ApplicationSettings.Default.XmrNetworkAddress) && _xmrSubscriber.LastHeartBeat != DateTime.MinValue) { + // Log when severly overdue a check + if (_xmrSubscriber.LastHeartBeat < DateTime.Now.AddHours(-1)) + { + Trace.WriteLine(new LogMessage("Schedule - OnScheduleManagerCheckComplete", "XMR heart beat last received over an hour ago.")); + } + // Check to see if the last update date was over 5 minutes ago - if (_xmrSubscriber.LastHeartBeat < DateTime.Now.AddSeconds(-90)) + if (_xmrSubscriber.LastHeartBeat < DateTime.Now.AddMinutes(-5)) { // Reconfigure it _registerAgent_OnXmrReconfigure(); @@ -288,6 +307,18 @@ void _scheduleManager_OnScheduleManagerCheckComplete() Trace.WriteLine(new LogMessage("Schedule - OnScheduleManagerCheckComplete", "Error = " + e.Message), LogType.Error.ToString()); } } + + /// + /// Are all the required agent threads alive? + /// + /// + private bool agentThreadsAlive() + { + return _registerAgentThread.IsAlive && + _scheduleAndRfAgentThread.IsAlive && + _logAgentThread.IsAlive && + _libraryAgentThread.IsAlive; + } /// /// XMR Reconfigure @@ -398,8 +429,7 @@ private void _requiredFilesAgent_OnFullyProvisioned() public void wakeUpXmds() { _registerAgent.WakeUp(); - _scheduleAgent.WakeUp(); - _requiredFilesAgent.WakeUp(); + _scheduleAndRfAgent.WakeUp(); _logAgent.WakeUp(); } @@ -493,7 +523,22 @@ private void LayoutFileModified(string layoutPath) if (_layoutSchedule[_currentLayout].layoutFile == ApplicationSettings.Default.LibraryPath + @"\" + layoutPath) { // What happens if the action of downloading actually invalidates this layout? - if (!_cacheManager.IsValidLayout(layoutPath)) + bool valid = _cacheManager.IsValidPath(layoutPath); + + if (valid) + { + // Check dependents + foreach (string dependent in _layoutSchedule[_currentLayout].Dependents) + { + if (!string.IsNullOrEmpty(dependent) && !_cacheManager.IsValidPath(dependent)) + { + valid = false; + break; + } + } + } + + if (!valid) { Trace.WriteLine(new LogMessage("Schedule - LayoutFileModified", "The current layout is now invalid, refreshing the current schedule."), LogType.Audit.ToString()); @@ -541,11 +586,8 @@ public void Stop() // Stop the register agent _registerAgent.Stop(); - // Stop the schedule agent - _scheduleAgent.Stop(); - // Stop the requiredfiles agent - _requiredFilesAgent.Stop(); + _scheduleAndRfAgent.Stop(); // Stop the Schedule Manager Thread _scheduleManager.Stop(); diff --git a/Logic/ScheduleItem.cs b/Logic/ScheduleItem.cs index f0ee286c..6fb4614d 100644 --- a/Logic/ScheduleItem.cs +++ b/Logic/ScheduleItem.cs @@ -24,5 +24,32 @@ public class ScheduleItem public DateTime ToDt; public List Dependents = new List(); + + /// + /// ToString + /// + /// + public override string ToString() + { + return string.Format("[{0}] From {1} to {2} with priority {3}. {4} dependents.", id, FromDt.ToString(), ToDt.ToString(), Priority, Dependents.Count); + } + + public override int GetHashCode() + { + return id + scheduleid; + } + + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + return false; + + ScheduleItem compare = obj as ScheduleItem; + + return id == compare.id && + scheduleid == compare.scheduleid && + FromDt.Ticks == compare.FromDt.Ticks && + ToDt.Ticks == compare.ToDt.Ticks; + } } } diff --git a/Logic/ScheduleManager.cs b/Logic/ScheduleManager.cs index 45a24207..f4d1c4d4 100644 --- a/Logic/ScheduleManager.cs +++ b/Logic/ScheduleManager.cs @@ -75,6 +75,11 @@ class ScheduleManager private bool _refreshSchedule; private CacheManager _cacheManager; private DateTime _lastScreenShotDate; + + /// + /// The currently playing layout Id + /// + private int _currenctLayoutId; /// /// Client Info Form @@ -152,6 +157,22 @@ public Collection CurrentOverlaySchedule return _currentOverlaySchedule; } } + + /// + /// Get or Set the current layout id + /// + public int CurrentLayoutId + { + get + { + return _currenctLayoutId; + } + set + { + lock (_locker) + _currenctLayoutId = value; + } + } #endregion @@ -235,7 +256,8 @@ public void Run() } // Write a flag to the status.xml file - File.WriteAllText(Path.Combine(ApplicationSettings.Default.LibraryPath, "status.json"), "{\"lastActivity\":\"" + DateTime.Now.ToString() + "\"}"); + if (OnScheduleManagerCheckComplete != null) + OnScheduleManagerCheckComplete(); } catch (Exception ex) { @@ -314,13 +336,30 @@ private bool IsNewScheduleAvailable() if (_currentSchedule.Count == 0) forceChange = true; + // Log + List currentScheduleString = new List(); + List newScheduleString = new List(); + // Are all the items that were in the _currentSchedule still there? foreach (ScheduleItem layout in _currentSchedule) { if (!newSchedule.Contains(layout)) + { + Trace.WriteLine(new LogMessage("ScheduleManager - IsNewScheduleAvailable", "New Schedule does not contain " + layout.id), LogType.Audit.ToString()); forceChange = true; + } + currentScheduleString.Add(layout.ToString()); + } + + foreach (ScheduleItem layout in _currentSchedule) + { + newScheduleString.Add(layout.ToString()); } + Trace.WriteLine(new LogMessage("ScheduleManager - IsNewScheduleAvailable", "Layouts in Current Schedule: " + string.Join(Environment.NewLine, currentScheduleString)), LogType.Audit.ToString()); + Trace.WriteLine(new LogMessage("ScheduleManager - IsNewScheduleAvailable", "Layouts in New Schedule: " + string.Join(Environment.NewLine, newScheduleString)), LogType.Audit.ToString()); + + // Are all the items that were in the _currentOverlaySchedule still there? foreach (ScheduleItem layout in _currentOverlaySchedule) { @@ -328,6 +367,7 @@ private bool IsNewScheduleAvailable() forceChange = true; } + // Set the new schedule _currentSchedule = newSchedule; @@ -374,11 +414,17 @@ private Collection LoadNewSchedule() // If we haven't already assessed this layout before, then check that it is valid if (!validLayoutIds.Contains(layout.id)) + { + if (!ApplicationSettings.Default.ExpireModifiedLayouts && layout.id == CurrentLayoutId) + { + Trace.WriteLine(new LogMessage("ScheduleManager - LoadNewSchedule", "Skipping validity test for current layout."), LogType.Audit.ToString()); + } + else { // Is the layout valid in the cachemanager? try { - if (!_cacheManager.IsValidLayout(layout.id + ".xlf")) + if (!_cacheManager.IsValidPath(layout.id + ".xlf")) { invalidLayouts.Add(layout.id); Trace.WriteLine(new LogMessage("ScheduleManager - LoadNewSchedule", "Layout invalid: " + layout.id), LogType.Info.ToString()); @@ -394,15 +440,22 @@ private Collection LoadNewSchedule() } // Check dependents + bool validDependents = true; foreach (string dependent in layout.Dependents) { - if (!_cacheManager.IsValidPath(dependent)) + if (!string.IsNullOrEmpty(dependent) && !_cacheManager.IsValidPath(dependent)) { invalidLayouts.Add(layout.id); Trace.WriteLine(new LogMessage("ScheduleManager - LoadNewSchedule", "Layout has invalid dependent: " + dependent), LogType.Info.ToString()); - continue; + + validDependents = false; + break; } } + + if (!validDependents) + continue; + } } // Add to the valid layout ids @@ -494,7 +547,7 @@ private Collection LoadNewOverlaySchedule() // Is the layout valid in the cachemanager? try { - if (!_cacheManager.IsValidLayout(layout.id + ".xlf")) + if (!_cacheManager.IsValidPath(layout.id + ".xlf")) { invalidLayouts.Add(layout.id); Trace.WriteLine(new LogMessage("ScheduleManager - LoadNewOverlaySchedule", "Layout invalid: " + layout.id), LogType.Info.ToString()); @@ -659,6 +712,15 @@ private ScheduleItem ParseNodeIntoScheduleItem(XmlNode node) temp.layoutFile = ApplicationSettings.Default.LibraryPath + @"\" + layoutFile + @".xlf"; temp.id = int.Parse(layoutFile); + // Dependents + if (attributes["dependents"] != null && !string.IsNullOrEmpty(attributes["dependents"].Value)) + { + foreach (string dependent in attributes["dependents"].Value.Split(',')) + { + temp.Dependents.Add(dependent); + } + } + // Get attributes that only exist on the default if (temp.NodeName != "default") { @@ -682,7 +744,6 @@ private ScheduleItem ParseNodeIntoScheduleItem(XmlNode node) // Add it to the layout schedule if (scheduleId != "") temp.scheduleid = int.Parse(scheduleId); - // Dependents if (attributes["dependents"] != null) { diff --git a/MainForm.Designer.cs b/MainForm.Designer.cs index 3b16c1a4..fff83877 100644 --- a/MainForm.Designer.cs +++ b/MainForm.Designer.cs @@ -44,7 +44,7 @@ private void InitializeComponent() this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; this.Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath); this.Name = "MainForm"; - this.Text = "Xibo Client"; + this.Text = Application.ProductName; this.WindowState = System.Windows.Forms.FormWindowState.Maximized; this.Load += new System.EventHandler(this.MainForm_Load); this.ResumeLayout(false); diff --git a/MainForm.cs b/MainForm.cs index bb09667c..86fbfbfa 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2006-15 Daniel Garner + * Copyright (C) 2006-16 Daniel Garner * * This file is part of Xibo. * @@ -39,6 +39,7 @@ using System.Globalization; using XiboClient.Logic; using XiboClient.Control; +using XiboClient.Error; namespace XiboClient { @@ -324,7 +325,7 @@ void MainForm_Shown(object sender, EventArgs e) _schedule = new Schedule(ApplicationSettings.Default.LibraryPath + @"\" + ApplicationSettings.Default.ScheduleFile, ref _cacheManager, ref _clientInfoForm); // Bind to the schedule change event - notifys of changes to the schedule - _schedule.ScheduleChangeEvent += new Schedule.ScheduleChangeDelegate(schedule_ScheduleChangeEvent); + _schedule.ScheduleChangeEvent += ScheduleChangeEvent; // Bind to the overlay change event _schedule.OverlayChangeEvent += ScheduleOverlayChangeEvent; @@ -446,7 +447,7 @@ private void SetCacheManager() /// Handles the ScheduleChange event /// /// - void schedule_ScheduleChangeEvent(string layoutPath, int scheduleId, int layoutId) + void ScheduleChangeEvent(string layoutPath, int scheduleId, int layoutId) { Trace.WriteLine(new LogMessage("MainForm - ScheduleChangeEvent", string.Format("Schedule Changing to {0}", layoutPath)), LogType.Audit.ToString()); @@ -517,6 +518,11 @@ private void ChangeToNextLayout(string layoutPath) PrepareLayout(layoutPath); _clientInfoForm.CurrentLayoutId = layoutPath; + _schedule.CurrentLayoutId = _layoutId; + } + catch (DefaultLayoutException) + { + throw; } catch (Exception e) { @@ -547,7 +553,8 @@ private void ChangeToNextLayout(string layoutPath) } catch (Exception ex) { - Trace.WriteLine(new LogMessage("MainForm - ChangeToNextLayout", "Layout Change to " + layoutPath + " failed. Exception raised was: " + ex.Message), LogType.Error.ToString()); + if (!(ex is DefaultLayoutException)) + Trace.WriteLine(new LogMessage("MainForm - ChangeToNextLayout", "Layout Change to " + layoutPath + " failed. Exception raised was: " + ex.Message), LogType.Error.ToString()); if (!_showingSplash) ShowSplashScreen(); @@ -593,7 +600,7 @@ private void PrepareLayout(string layoutPath) // Default or not if (layoutPath == ApplicationSettings.Default.LibraryPath + @"\Default.xml" || String.IsNullOrEmpty(layoutPath)) { - throw new Exception("Default layout"); + throw new DefaultLayoutException(); } else { @@ -613,6 +620,7 @@ private void PrepareLayout(string layoutPath) } catch (IOException ioEx) { + _cacheManager.Remove(layoutPath); Trace.WriteLine(new LogMessage("MainForm - PrepareLayout", "IOException: " + ioEx.ToString()), LogType.Error.ToString()); throw; } @@ -686,7 +694,7 @@ private void PrepareLayout(string layoutPath) GenerateBackgroundImage(layoutAttributes["background"].Value, backgroundWidth, backgroundHeight, bgFilePath); BackgroundImage = new Bitmap(bgFilePath); - options.backgroundImage = bgFilePath; + options.backgroundImage = @"/backgrounds/" + backgroundWidth + "x" + backgroundHeight + "_" + layoutAttributes["background"].Value; ; } else { @@ -752,6 +760,20 @@ private void PrepareLayout(string layoutPath) continue; } + // Region loop setting + options.RegionLoop = false; + + XmlNode regionOptionsNode = region.SelectSingleNode("options"); + + if (regionOptionsNode != null) + { + foreach (XmlNode option in regionOptionsNode.ChildNodes) + { + if (option.Name == "loop" && option.InnerText == "1") + options.RegionLoop = true; + } + } + //each region XmlAttributeCollection nodeAttibutes = region.Attributes; @@ -797,9 +819,6 @@ private void PrepareLayout(string layoutPath) // We have loaded a layout and therefore are no longer showing the splash screen _showingSplash = false; - // We have loaded a layout and therefore are no longer showing the splash screen - _showingSplash = false; - // Null stuff listRegions = null; listMedia = null; diff --git a/Media/Flash.cs b/Media/Flash.cs index 032053e2..5329b423 100644 --- a/Media/Flash.cs +++ b/Media/Flash.cs @@ -92,6 +92,7 @@ private void GenerateHeadHtml() } else { + string bgImage = ApplicationSettings.Default.LibraryPath + @"/" + _backgroundImage; bodyStyle = "background-image: url('" + _backgroundImage + "'); background-attachment:fixed; background-color:" + _backgroundColor + " background-repeat: no-repeat; background-position: " + _backgroundLeft + " " + _backgroundTop + ";"; } diff --git a/Media/IeWebMedia.cs b/Media/IeWebMedia.cs index f162fc6c..96b15a55 100644 --- a/Media/IeWebMedia.cs +++ b/Media/IeWebMedia.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2014 Spring Signage Ltd + * Copyright (C) 2014-2016 Spring Signage Ltd * * This file is part of Xibo. * @@ -264,7 +264,7 @@ private void xmds_GetResourceCompleted(object sender, XiboClient.xmds.GetResourc } else { - bodyStyle = "background-image: url('" + _options.backgroundImage.Replace('\\', '/') + "'); background-attachment:fixed; background-color:" + backgroundColor + "; background-repeat: no-repeat; background-position: " + _options.backgroundLeft + "px " + _options.backgroundTop + "px;"; + bodyStyle = "background-image: url('" + _options.backgroundImage + "'); background-attachment:fixed; background-color:" + backgroundColor + "; background-repeat: no-repeat; background-position: " + _options.backgroundLeft + "px " + _options.backgroundTop + "px;"; } string html = cachedFile.Replace("", ""); @@ -336,7 +336,7 @@ private void UpdateCacheIfNecessary() } else { - bodyStyle = "background-image: url('" + _options.backgroundImage.Replace('\\', '/') + "'); background-attachment:fixed; background-color:" + backgroundColor + "; background-repeat: no-repeat; background-position: " + _options.backgroundLeft + "px " + _options.backgroundTop + "px;"; + bodyStyle = "background-image: url('" + _options.backgroundImage + "'); background-attachment:fixed; background-color:" + backgroundColor + "; background-repeat: no-repeat; background-position: " + _options.backgroundLeft + "px " + _options.backgroundTop + "px;"; } string html = cachedFile.Replace("", ""); @@ -378,9 +378,9 @@ protected override void Dispose(bool disposing) _webBrowser.Navigate("about:blank"); _webBrowser.Dispose(); } - catch + catch (Exception e) { - Debug.WriteLine(new LogMessage("WebBrowser still in use.", String.Format("Dispose"))); + Trace.WriteLine(new LogMessage("IeWebMedia - Dispose", "Cannot dispose of web browser. E = " + e.Message), LogType.Info.ToString()); } } diff --git a/Media/ShellCommand.cs b/Media/ShellCommand.cs index 423b67bb..34e9a833 100644 --- a/Media/ShellCommand.cs +++ b/Media/ShellCommand.cs @@ -1,6 +1,6 @@ /* * Xibo - Digitial Signage - http://www.xibo.org.uk - * Copyright (C) 2012 Daniel Garner + * Copyright (C) 2012-16 Daniel Garner * * This file is part of Xibo. * @@ -30,12 +30,23 @@ class ShellCommand : Media { string _command = ""; string _code = ""; + bool _launchThroughCmd = true; + bool _terminateCommand = false; + bool _useTaskKill = false; + int _processId; public ShellCommand(RegionOptions options) : base(options.width, options.height, options.top, options.left) { _command = Uri.UnescapeDataString(options.Dictionary.Get("windowsCommand")).Replace('+', ' '); _code = options.Dictionary.Get("commandCode"); + + // Default to launching through CMS for backwards compatiblity + _launchThroughCmd = (options.Dictionary.Get("launchThroughCmd", "1") == "1"); + + // Termination + _terminateCommand = (options.Dictionary.Get("terminateCommand") == "1"); + _useTaskKill = (options.Dictionary.Get("useTaskkill") == "1"); } public override void RenderMedia() @@ -122,14 +133,88 @@ private void ExecuteShellCommand() { ProcessStartInfo startInfo = new ProcessStartInfo(); + if (_launchThroughCmd) + { + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + startInfo.Arguments = "/C " + _command; + } + else + { + // Split the command into a command string and arguments. + string[] splitCommand = _command.Split(new[] { ' ' }, 2); + startInfo.FileName = splitCommand[0]; + + if (splitCommand.Length > 1) + startInfo.Arguments = splitCommand[1]; + } + + process.StartInfo = startInfo; + process.Start(); + + // Grab the ID + _processId = process.Id; + } + } + } + + /// + /// Terminates the shell command + /// + private void TerminateCommand() + { + Trace.WriteLine(new LogMessage("ShellCommand - TerminateCommand", _command), LogType.Info.ToString()); + + if (_processId == 0) + { + Trace.WriteLine(new LogMessage("ShellCommand - TerminateCommand", "ProcessID empty for command: " + _command), LogType.Error.ToString()); + return; + } + + if (_useTaskKill) + { + using (Process process = new Process()) + { + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - startInfo.Arguments = "/C " + _command; + startInfo.FileName = "taskkill.exe"; + startInfo.Arguments = "/pid " + _processId.ToString(); process.StartInfo = startInfo; process.Start(); } } + else + { + using (Process process = Process.GetProcessById(_processId)) + { + process.Kill(); + } + } + } + + /// + /// Dispose of this text item + /// + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + // Remove the webbrowser control + try + { + // Terminate the command + TerminateCommand(); + } + catch + { + Debug.WriteLine(new LogMessage("Unable to terminate command", "Dispose")); + } + } + + base.Dispose(disposing); } } } diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 63a5c671..bc48410b 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -30,6 +30,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.0.0")] +[assembly: AssemblyVersion("10.8.0.1")] +[assembly: AssemblyFileVersion("10.8.0.1")] [assembly: NeutralResourcesLanguageAttribute("en-GB")] diff --git a/XiboClient.csproj b/XiboClient.csproj index 51aa8ee5..a3124782 100644 --- a/XiboClient.csproj +++ b/XiboClient.csproj @@ -80,28 +80,31 @@ false - - packages\AsyncIO.0.1.18.0\lib\net40\AsyncIO.dll + + packages\AsyncIO.0.1.26.0\lib\net40\AsyncIO.dll + True False wmpdll\AxInterop.WMPLib.dll False - - packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll + + packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll + True False False wmpdll\Interop.WMPLib.dll - - packages\NetMQ.3.3.2.2\lib\net40\NetMQ.dll + + packages\NetMQ.3.3.3.4\lib\net40\NetMQ.dll + True - - False - packages\Newtonsoft.Json.8.0.3\lib\net40\Newtonsoft.Json.dll + + packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True @@ -112,8 +115,9 @@ - - packages\EmbedIO.1.0.17\lib\Unosquare.Labs.EmbedIO.dll + + packages\EmbedIO.1.0.24\lib\Unosquare.Labs.EmbedIO.dll + True @@ -124,6 +128,7 @@ + Form @@ -256,8 +261,7 @@ - - + @@ -340,4 +344,4 @@ --> - + \ No newline at end of file diff --git a/XmdsAgents/FileAgent.cs b/XmdsAgents/FileAgent.cs index 068938d4..b6d13fce 100644 --- a/XmdsAgents/FileAgent.cs +++ b/XmdsAgents/FileAgent.cs @@ -295,6 +295,9 @@ public void Run() } catch (WebException webEx) { + // Remove from the cache manager + _requiredFiles.CurrentCacheManager.Remove(file.SaveAs); + // Log this message, but dont abort the thread Trace.WriteLine(new LogMessage("FileAgent - Run", "Web Exception in Run: " + webEx.Message), LogType.Info.ToString()); @@ -303,6 +306,9 @@ public void Run() } catch (Exception ex) { + // Remove from the cache manager + _requiredFiles.CurrentCacheManager.Remove(file.SaveAs); + // Log this message, but dont abort the thread Trace.WriteLine(new LogMessage("FileAgent - Run", "Exception in Run: " + ex.Message), LogType.Error.ToString()); diff --git a/XmdsAgents/RequiredFilesAgent.cs b/XmdsAgents/ScheduleAndFilesAgent.cs similarity index 80% rename from XmdsAgents/RequiredFilesAgent.cs rename to XmdsAgents/ScheduleAndFilesAgent.cs index 10f6eef0..1dd0eedc 100644 --- a/XmdsAgents/RequiredFilesAgent.cs +++ b/XmdsAgents/ScheduleAndFilesAgent.cs @@ -35,7 +35,7 @@ namespace XiboClient.XmdsAgents { - class RequiredFilesAgent + class ScheduleAndFilesAgent { private static object _locker = new object(); private bool _forceStop = false; @@ -54,6 +54,30 @@ class RequiredFilesAgent private RequiredFiles _requiredFiles; private Semaphore _fileDownloadLimit; + /// + /// Current Schedule Manager for this Xibo Client + /// + public ScheduleManager CurrentScheduleManager + { + set + { + _scheduleManager = value; + } + } + private ScheduleManager _scheduleManager; + + /// + /// Schedule File Location + /// + public string ScheduleLocation + { + set + { + _scheduleLocation = value; + } + } + private string _scheduleLocation; + /// /// Client Hardware key /// @@ -93,7 +117,7 @@ public ClientInfo ClientInfoForm /// /// Required Files Agent /// - public RequiredFilesAgent() + public ScheduleAndFilesAgent() { _fileDownloadLimit = new Semaphore(ApplicationSettings.Default.MaxConcurrentDownloads, ApplicationSettings.Default.MaxConcurrentDownloads); _requiredFiles = new RequiredFiles(); @@ -125,15 +149,18 @@ public void Run() while (!_forceStop) { + // If we are restarting, reset + _manualReset.Reset(); + lock (_locker) { + // Run the schedule Agent thread + scheduleAgent(); + if (ApplicationSettings.Default.InDownloadWindow) { try { - // If we are restarting, reset - _manualReset.Reset(); - int filesToDownload = _requiredFiles.FilesDownloading; // If we are currently downloading something, we have to wait @@ -339,7 +366,7 @@ private void reportStorage() { using (xmds.xmds xmds = new xmds.xmds()) { - string status = "{\"availableSpace\":\"" + drive.TotalFreeSpace + "\", \"totalSpace\":\"" + drive.TotalSize + "\"}"; + string status = "{\"availableSpace\":\"" + drive.TotalFreeSpace + "\", \"totalSpace\":\"" + drive.TotalSize + "\", \"deviceName\":\"" + Environment.MachineName + "\"}"; xmds.Credentials = null; xmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds; @@ -351,5 +378,71 @@ private void reportStorage() } } } + + /// + /// Schedule Agent + /// + private void scheduleAgent() + { + try + { + // If we are restarting, reset + _manualReset.Reset(); + + Trace.WriteLine(new LogMessage("ScheduleAgent - Run", "Thread Woken and Lock Obtained"), LogType.Audit.ToString()); + + _clientInfoForm.ScheduleStatus = "Running: Get Data from Xibo Server"; + + using (xmds.xmds xmds = new xmds.xmds()) + { + xmds.Credentials = null; + xmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds; + xmds.UseDefaultCredentials = false; + + string scheduleXml = xmds.Schedule(ApplicationSettings.Default.ServerKey, _hardwareKey); + + // Set the flag to indicate we have a connection to XMDS + ApplicationSettings.Default.XmdsLastConnection = DateTime.Now; + + _clientInfoForm.ScheduleStatus = "Running: Data Received"; + + // Hash of the result + string md5NewSchedule = Hashes.MD5(scheduleXml); + string md5CurrentSchedule = Hashes.MD5(ScheduleManager.GetScheduleXmlString(_scheduleLocation)); + + // Compare the results of the HASH + if (md5CurrentSchedule != md5NewSchedule) + { + Trace.WriteLine(new LogMessage("Schedule Agent - Run", "Received new schedule")); + + _clientInfoForm.ScheduleStatus = "Running: New Schedule Received"; + + // Write the result to the schedule xml location + ScheduleManager.WriteScheduleXmlToDisk(_scheduleLocation, scheduleXml); + + // Indicate to the schedule manager that it should read the XML file + _scheduleManager.RefreshSchedule = true; + } + + _clientInfoForm.ScheduleStatus = "Sleeping"; + } + } + catch (WebException webEx) + { + // Increment the quantity of XMDS failures and bail out + ApplicationSettings.Default.IncrementXmdsErrorCount(); + + // Log this message, but dont abort the thread + Trace.WriteLine(new LogMessage("ScheduleAgent - Run", "WebException in Run: " + webEx.Message), LogType.Info.ToString()); + + _clientInfoForm.ScheduleStatus = "Error: " + webEx.Message; + } + catch (Exception ex) + { + // Log this message, but dont abort the thread + Trace.WriteLine(new LogMessage("ScheduleAgent - Run", "Exception in Run: " + ex.Message), LogType.Error.ToString()); + _clientInfoForm.ScheduleStatus = "Error. " + ex.Message; + } + } } } diff --git a/app.config b/app.config index 878ccccc..d26435b2 100644 --- a/app.config +++ b/app.config @@ -1,17 +1,17 @@ - + - + - - + + - + diff --git a/bin/x86/Release/Xibo.scr b/bin/x86/Release/Xibo.scr index 7c1d0045..b684ed6a 100644 Binary files a/bin/x86/Release/Xibo.scr and b/bin/x86/Release/Xibo.scr differ diff --git a/packages.config b/packages.config index 973b6881..0b43064f 100644 --- a/packages.config +++ b/packages.config @@ -1,8 +1,8 @@  - - - - - + + + + + \ No newline at end of file