//******************************************************************************************************
// StatHistorianReader.cs - Gbtc
//
// Copyright © 2014, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 02/11/2014 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using GSF;
using GSF.Configuration;
using GSF.Diagnostics;
using GSF.IO;
using Microsoft.Win32;
// ReSharper disable UnusedMember.Global
namespace StatHistorianReportGenerator
{
///
/// Searches the registry as well as well-known locations for openHistorian 1.0 archives.
///
public class ArchiveLocator
{
#region [ Members ]
// Fields
private string m_archiveLocation;
private string m_archiveLocationName;
private string m_archiveName;
#endregion
#region [ Properties ]
///
/// Gets or sets the archive location. Querying this property without
/// setting it will initiate a search of the registry and well-known
/// locations where the archive might be located.
///
public string ArchiveLocation
{
get
{
if (string.IsNullOrEmpty(m_archiveLocation))
InitializeArchiveLocation();
return m_archiveLocation;
}
set
{
m_archiveLocation = value;
}
}
///
/// Gets or sets the name of the folder containing the
/// archive files to facilitate more accurate searching.
///
public string ArchiveLocationName
{
get
{
return m_archiveLocationName;
}
set
{
m_archiveLocationName = value;
}
}
///
/// Gets or sets the name of the archive to be searched for.
///
public string ArchiveName
{
get
{
return m_archiveName;
}
set
{
m_archiveName = value;
}
}
///
/// Gets the name of the archive file.
///
public string ArchiveFileName
{
get
{
if ((object)m_archiveName == null)
return null;
return m_archiveName + "_archive.d";
}
}
///
/// Gets the name of the metadata file.
///
public string MetadataFileName
{
get
{
if ((object)m_archiveName == null)
return null;
return m_archiveName + "_dbase.dat";
}
}
///
/// Gets the name of the state file.
///
public string StateFileName
{
get
{
if ((object)m_archiveName == null)
return null;
return m_archiveName + "_startup.dat";
}
}
///
/// Gets the name of the intercom file.
///
public string IntercomFileName
{
get
{
if ((object)m_archiveName == null)
return null;
return "scratch.dat";
}
}
///
/// Gets the full path to the archive file.
///
public string ArchiveFilePath
{
get
{
return Path.Combine(ArchiveLocation, ArchiveFileName);
}
}
///
/// Gets the full path to the metadata file.
///
public string MetadataFilePath
{
get
{
return Path.Combine(ArchiveLocation, MetadataFileName);
}
}
///
/// Gets the full path to the state file.
///
public string StateFilePath
{
get
{
return Path.Combine(ArchiveLocation, StateFileName);
}
}
///
/// Gets the full path to the intercom file.
///
public string IntercomFilePath
{
get
{
return Path.Combine(ArchiveLocation, IntercomFileName);
}
}
#endregion
#region [ Methods ]
// Initiates the search for the archive location.
// This will check the configuration file setting before
// resorting to the registry or searching well-known locations.
private void InitializeArchiveLocation()
{
CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Current.Settings["systemSettings"];
CategorizedSettingsElement archiveLocationSetting = systemSettings["ArchiveLocation"];
if ((object)archiveLocationSetting != null)
m_archiveLocation = FilePath.GetAbsolutePath(archiveLocationSetting.Value);
if (string.IsNullOrEmpty(m_archiveLocation) && !TryLocateArchive(out m_archiveLocation))
throw new DirectoryNotFoundException($"Unable to locate {m_archiveName.ToNonNullString().ToUpper()} archive. Please check the configuration file to set the archive location.");
}
// Attempts to locate the archive by searching the registry and well-known locations.
private bool TryLocateArchive(out string archiveLocation)
{
if (IsArchiveLocation(m_archiveLocationName))
{
archiveLocation = m_archiveLocationName;
return true;
}
return SearchConfigFiles(out archiveLocation) || SearchRegistry(out archiveLocation) || SearchProgramFiles(out archiveLocation);
}
// Searches local config files to find the archive location.
private bool SearchConfigFiles(out string archiveLocation)
{
try
{
XDocument serviceConfig = XDocument.Load(GetConfigurationFileName());
string archiveFileName = serviceConfig
.Descendants($"{ArchiveName.ToLowerInvariant()}ArchiveFile")
.SelectMany(systemSettings => systemSettings.Elements("add"))
.Where(element => "FileName".Equals((string)element.Attribute("name"), StringComparison.OrdinalIgnoreCase))
.Select(element => (string)element.Attribute("value"))
.FirstOrDefault();
if (!string.IsNullOrWhiteSpace(archiveFileName) && File.Exists(archiveFileName))
{
archiveLocation = Path.GetDirectoryName(archiveFileName);
return true;
}
}
catch (Exception ex)
{
Program.Log.Publish(MessageLevel.Warning, "SearchConfigFiles", "Failed search local config files for archive location.", exception: ex);
}
archiveLocation = null;
return false;
}
// Searches the registry to find the archive location.
private bool SearchRegistry(out string archiveLocation)
{
RegistryKey gpaKey;
try
{
gpaKey = Registry.LocalMachine.OpenSubKey(@"Software\Grid Protection Alliance");
if ((object)gpaKey != null)
{
archiveLocation = gpaKey.GetSubKeyNames()
.Select(name => TryOpenSubKey(gpaKey, name))
.Where(productKey => (object)productKey != null)
.Select(productKey => productKey.GetValue("InstallPath") as string)
.Where(installPath => !string.IsNullOrEmpty(installPath))
.Select(installPath => Path.Combine(installPath, m_archiveLocationName))
.FirstOrDefault(IsArchiveLocation);
return !string.IsNullOrWhiteSpace(archiveLocation);
}
}
catch (Exception ex)
{
Program.Log.Publish(MessageLevel.Warning, "SearchRegistry", "Failed search registry for archive location.", exception: ex);
}
archiveLocation = null;
return false;
}
// Searches the Program Files folder for well-known, default statistics archive locations.
private bool SearchProgramFiles(out string archiveLocation)
{
try
{
archiveLocation = Directory.GetDirectories(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles))
.Select(programPath => Path.Combine(programPath, m_archiveLocationName))
.FirstOrDefault(IsArchiveLocation);
return !string.IsNullOrWhiteSpace(archiveLocation);
}
catch (Exception ex)
{
Program.Log.Publish(MessageLevel.Warning, "SearchProgramFiles", "Failed search program files for archive location.", exception: ex);
}
archiveLocation = null;
return false;
}
// Determines if the given path points to a valid archive location.
private bool IsArchiveLocation(string path)
{
string fullPath;
try
{
fullPath = FilePath.GetAbsolutePath(path);
if (Directory.Exists(fullPath))
{
return File.Exists(Path.Combine(fullPath, ArchiveFileName))
&& File.Exists(Path.Combine(fullPath, MetadataFileName))
&& File.Exists(Path.Combine(fullPath, StateFileName))
&& File.Exists(Path.Combine(fullPath, IntercomFileName));
}
}
catch (Exception ex)
{
Program.Log.Publish(MessageLevel.Warning, "IsArchiveLocation", "Failed test path for valid archive location.", exception: ex);
}
return false;
}
#endregion
#region [ Static ]
// Static Properties
public static string HostConfigFile;
// Static Methods
// Attempts to open a sub key of the given registry key and returns null if it fails.
private static RegistryKey TryOpenSubKey(RegistryKey key, string name)
{
try
{
return key.OpenSubKey(name);
}
catch
{
return null;
}
}
public static string GetConfigurationFileName()
{
if (!string.IsNullOrEmpty(HostConfigFile) && File.Exists(HostConfigFile))
return HostConfigFile;
// Will be faster to load known config file, try a few common ones first
string[] knownConfigurationFileNames =
{
"openPDC.exe.config",
"SIEGate.exe.config",
"openHistorian.exe.config",
"substationSBG.exe.config",
"openMIC.exe.config",
"PDQTracker.exe.config",
"openECA.exe.config"
};
// Search for the file name in the list of known configuration files
foreach (string fileName in knownConfigurationFileNames)
{
string absolutePath = FilePath.GetAbsolutePath(fileName);
if (File.Exists(absolutePath))
return absolutePath;
}
// Fall back on manual search
foreach (string configFile in FilePath.GetFileList($"{FilePath.AddPathSuffix(FilePath.GetAbsolutePath(""))}*.exe.config"))
{
try
{
XDocument serviceConfig = XDocument.Load(configFile);
string applicationName = serviceConfig
.Descendants("securityProvider")
.SelectMany(systemSettings => systemSettings.Elements("add"))
.Where(element => "ApplicationName".Equals((string)element.Attribute("name"), StringComparison.OrdinalIgnoreCase))
.Select(element => (string)element.Attribute("value"))
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(applicationName))
continue;
if (applicationName.Trim().Equals(FilePath.GetFileNameWithoutExtension(FilePath.GetFileNameWithoutExtension(configFile)), StringComparison.OrdinalIgnoreCase))
return configFile;
}
catch (Exception ex)
{
// Just move on to the next config file if XML parsing fails
Program.Log.Publish(MessageLevel.Warning, "GetConfigurationFileName", $"Failed parse config file \"{configFile}\".", exception: ex);
}
}
return ConfigurationFile.Current.Configuration.FilePath;
}
#endregion
}
}