Tested DevEnv Interface, fixed up several Code Contract errors / misuses.
Added Export as an DevEnv interface function, exporting a single object to txt. Builds a filter string and writes the whole stuff out. Works like a breeze. Added Charset conversion for finsql.exe result values (command output and error log), which uses something similar (but not quite) to CP850. For any error message, this should be enough, however, be aware that the format of the TXT files generated by ExportObject is in the same strange encoding. Details can be found here: http://forum.mibuso.com/discussion/37078/encoding-of-exported-navision-objects-txt-files It should be considered if it makes sense to normalize the objects as best as possible into the Unicode plane and convert them back on exit - though this will probably generate a whole different set problems when automating Build jobs. For now, leave it as-is, better safe than sorry.
This commit is contained in:
parent
98a044e2de
commit
566d727297
@ -41,7 +41,7 @@
|
|||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking>
|
<CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking>
|
||||||
<CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface>
|
<CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface>
|
||||||
<CodeContractsRuntimeThrowOnFailure>False</CodeContractsRuntimeThrowOnFailure>
|
<CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure>
|
||||||
<CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires>
|
<CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires>
|
||||||
<CodeContractsRuntimeSkipQuantifiers>False</CodeContractsRuntimeSkipQuantifiers>
|
<CodeContractsRuntimeSkipQuantifiers>False</CodeContractsRuntimeSkipQuantifiers>
|
||||||
<CodeContractsRunCodeAnalysis>True</CodeContractsRunCodeAnalysis>
|
<CodeContractsRunCodeAnalysis>True</CodeContractsRunCodeAnalysis>
|
||||||
|
@ -29,20 +29,20 @@ namespace NavScm.TestHost
|
|||||||
log.InfoFormat("{0} total entries in NavSqlObjects", NavSqlObjects.Count());
|
log.InfoFormat("{0} total entries in NavSqlObjects", NavSqlObjects.Count());
|
||||||
|
|
||||||
var query = from sql in NavSqlObjects
|
var query = from sql in NavSqlObjects
|
||||||
where sql.Modified == 1
|
where sql.Modified == 1 && sql.Type > 0
|
||||||
select sql;
|
select sql;
|
||||||
|
|
||||||
int count = query.Count();
|
int count = query.Count();
|
||||||
|
|
||||||
log.InfoFormat("{0} modified objects detected, reading them...", count);
|
log.InfoFormat("{0} modified objects detected, reading them into the cache...", count);
|
||||||
|
|
||||||
var foundObjects = new Dictionary<string, NavObject>(count);
|
var foundObjects = new Dictionary<string, NavObject>(count);
|
||||||
|
|
||||||
int i = 1;
|
// int i = 1;
|
||||||
foreach (NavObject o in query)
|
foreach (NavObject o in query)
|
||||||
{
|
{
|
||||||
log.DebugFormat("Row {6}/{7}: Type {0}, ID {1}, Name {2}, Modified {3} {4}, Version {5}",
|
//log.DebugFormat("Row {6}/{7}: Type {0}, ID {1}, Name {2}, Modified {3} {4}, Version {5}",
|
||||||
o.Type, o.ID, o.Name, o.Date.ToShortDateString(), o.Time.ToShortTimeString(), o.Version_List, i++, count);
|
// o.Type, o.ID, o.Name, o.Date.ToShortDateString(), o.Time.ToShortTimeString(), o.Version_List, i++, count);
|
||||||
|
|
||||||
foundObjects.Add(o.CacheKey, o);
|
foundObjects.Add(o.CacheKey, o);
|
||||||
}
|
}
|
||||||
@ -59,12 +59,21 @@ namespace NavScm.TestHost
|
|||||||
XmlDictionaryReader xmlReader = XmlDictionaryReader.CreateTextReader(reader, new XmlDictionaryReaderQuotas());
|
XmlDictionaryReader xmlReader = XmlDictionaryReader.CreateTextReader(reader, new XmlDictionaryReaderQuotas());
|
||||||
var loadedObjects = (Dictionary<string, NavObject>)serializer.ReadObject(xmlReader, true);
|
var loadedObjects = (Dictionary<string, NavObject>)serializer.ReadObject(xmlReader, true);
|
||||||
|
|
||||||
log.Debug("Dumping sample object 1.82004");
|
// 5.99997 => CU TN_Test
|
||||||
|
|
||||||
NavObject o2 = loadedObjects["1.82004"];
|
log.Debug("Dumping sample object descriptor for 5.99997 ");
|
||||||
|
|
||||||
|
NavObject o2 = loadedObjects["5.99997"];
|
||||||
log.DebugFormat("Type {0}, ID {1}, Name {2}, Modified {3} {4}, Version {5}",
|
log.DebugFormat("Type {0}, ID {1}, Name {2}, Modified {3} {4}, Version {5}",
|
||||||
o2.Type, o2.ID, o2.Name, o2.Date.ToShortDateString(), o2.Time.ToShortTimeString(), o2.Version_List);
|
o2.Type, o2.ID, o2.Name, o2.Date.ToShortDateString(), o2.Time.ToShortTimeString(), o2.Version_List);
|
||||||
|
|
||||||
|
DevEnvInterface devenv = new DevEnvInterface("C:\\Program Files (x86)\\Microsoft Dynamics NAV\\tbrt-nav-erp-02\\RoleTailored Client\\finsql.exe",
|
||||||
|
"tbrt-sql-erp-01", "TERRABIT 2015 DEV");
|
||||||
|
|
||||||
|
devenv.Export(loadedObjects["5.80"], $"{Directory.GetCurrentDirectory()}\\CU80.txt");
|
||||||
|
devenv.Export(loadedObjects["5.99997"], $"{Directory.GetCurrentDirectory()}\\CU99997.txt");
|
||||||
|
devenv.Export(loadedObjects["1.13"], $"{Directory.GetCurrentDirectory()}\\TAB13.txt");
|
||||||
|
|
||||||
log.Info("Shutting down...");
|
log.Info("Shutting down...");
|
||||||
|
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
@ -19,7 +19,7 @@ namespace NavScm.NavInterface
|
|||||||
/// <para>Currently, the interface expects to be able to access the database using
|
/// <para>Currently, the interface expects to be able to access the database using
|
||||||
/// NTLM Single Sign on. SQL user/pass authentication is not supported.</para>
|
/// NTLM Single Sign on. SQL user/pass authentication is not supported.</para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
class DevEnvInterface
|
public class DevEnvInterface
|
||||||
{
|
{
|
||||||
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(DevEnvInterface));
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(DevEnvInterface));
|
||||||
|
|
||||||
@ -31,23 +31,23 @@ namespace NavScm.NavInterface
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The exit code of finsql.exe. So far this is always 0, tests pending.
|
/// The exit code of finsql.exe. So far this is always 0, tests pending.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ExitCode;
|
public readonly int ExitCode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True, if the command executed successfully.
|
/// True, if the command executed successfully.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Success;
|
public readonly bool Success;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The output produced by the command, as written to navcommandresult.txt by finsql.exe.
|
/// The output produced by the command, as written to navcommandresult.txt by finsql.exe.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string CommandOutput;
|
public readonly string CommandOutput;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The errormessage if any, as written to the log file passed to finsql.exe. Empty, if
|
/// The errormessage if any, as written to the log file passed to finsql.exe. Empty, if
|
||||||
/// the call was successful.
|
/// the call was successful.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ErrorMessage;
|
public readonly string ErrorMessage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs the whole object.
|
/// Constructs the whole object.
|
||||||
@ -117,9 +117,6 @@ namespace NavScm.NavInterface
|
|||||||
/// <returns>The full path to the created directory, which is empty.</returns>
|
/// <returns>The full path to the created directory, which is empty.</returns>
|
||||||
protected string GetNewTempDirectory()
|
protected string GetNewTempDirectory()
|
||||||
{
|
{
|
||||||
Contract.Ensures(Directory.Exists(Contract.Result<string>()));
|
|
||||||
Contract.Ensures(! Directory.EnumerateFileSystemEntries(Contract.Result<string>()).Any());
|
|
||||||
|
|
||||||
// Try a few times. Uniqueness should not be a problem, as GetRandomFileName is cryptographically strong,
|
// Try a few times. Uniqueness should not be a problem, as GetRandomFileName is cryptographically strong,
|
||||||
// but beware of other errors e.g. during director creation.
|
// but beware of other errors e.g. during director creation.
|
||||||
// Note, that in theory an race condition is possible here in case GetRandomFileName doesn't behave.
|
// Note, that in theory an race condition is possible here in case GetRandomFileName doesn't behave.
|
||||||
@ -146,6 +143,8 @@ namespace NavScm.NavInterface
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Contract.Assert(!Directory.EnumerateFileSystemEntries(tempPath).Any());
|
||||||
|
|
||||||
return tempPath;
|
return tempPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +172,6 @@ namespace NavScm.NavInterface
|
|||||||
Contract.Requires(command != "");
|
Contract.Requires(command != "");
|
||||||
|
|
||||||
string tempPath = GetNewTempDirectory();
|
string tempPath = GetNewTempDirectory();
|
||||||
Contract.Ensures(!Directory.Exists(tempPath));
|
|
||||||
|
|
||||||
string errorLog = $"{tempPath}\\error.log";
|
string errorLog = $"{tempPath}\\error.log";
|
||||||
string commandOutput = $"{tempPath}\\navcommandresult.txt";
|
string commandOutput = $"{tempPath}\\navcommandresult.txt";
|
||||||
@ -184,7 +182,6 @@ namespace NavScm.NavInterface
|
|||||||
|
|
||||||
// Execute finsql.exe and wait for its exit...
|
// Execute finsql.exe and wait for its exit...
|
||||||
Process process = new Process();
|
Process process = new Process();
|
||||||
Contract.Ensures(process.HasExited);
|
|
||||||
|
|
||||||
process.StartInfo.FileName = DevEnvPath;
|
process.StartInfo.FileName = DevEnvPath;
|
||||||
process.StartInfo.Arguments = fullArguments;
|
process.StartInfo.Arguments = fullArguments;
|
||||||
@ -207,18 +204,22 @@ namespace NavScm.NavInterface
|
|||||||
log.Debug($"ExecuteCommand: Execution took: {sw.Elapsed}");
|
log.Debug($"ExecuteCommand: Execution took: {sw.Elapsed}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse and store finsql.exe result information
|
// Parse and store finsql.exe result information, be aware, that we are not getting
|
||||||
|
// Unicode back from finsql.exe, this is CP850 instead. Interesting, all this in the
|
||||||
|
// current millenia, as CP850 was introduced 1987 with DOS 3.3... Not to mention that
|
||||||
|
// the current windows ANSI Encoding is ignored as well. (Checked up to NAV 2015).
|
||||||
|
var finsqlEncoding = Encoding.GetEncoding(850);
|
||||||
CommandResult result;
|
CommandResult result;
|
||||||
if (File.Exists(errorLog))
|
if (File.Exists(errorLog))
|
||||||
{
|
{
|
||||||
result = new CommandResult(process.ExitCode, false, File.ReadAllText(commandOutput), File.ReadAllText(errorLog));
|
result = new CommandResult(process.ExitCode, false, File.ReadAllText(commandOutput, finsqlEncoding), File.ReadAllText(errorLog, finsqlEncoding));
|
||||||
log.ErrorFormat("ExecuteCommand: Command failed with: {0}", result.ErrorMessage);
|
log.ErrorFormat("ExecuteCommand: Command failed with: {0}", result.ErrorMessage);
|
||||||
log.DebugFormat("ExecuteCommand: Command result: {0}", result.CommandOutput);
|
log.DebugFormat("ExecuteCommand: Command result: {0}", result.CommandOutput);
|
||||||
log.DebugFormat("ExecuteCommand: finsql.exe finished with exit code {0}", process.ExitCode);
|
log.DebugFormat("ExecuteCommand: finsql.exe finished with exit code {0}", process.ExitCode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = new CommandResult(process.ExitCode, true, File.ReadAllText(commandOutput), "");
|
result = new CommandResult(process.ExitCode, true, File.ReadAllText(commandOutput, finsqlEncoding), "");
|
||||||
log.Info("ExecuteCommand: Command executed successfully");
|
log.Info("ExecuteCommand: Command executed successfully");
|
||||||
log.DebugFormat("ExecuteCommand: Command result: {0}", result.CommandOutput);
|
log.DebugFormat("ExecuteCommand: Command result: {0}", result.CommandOutput);
|
||||||
}
|
}
|
||||||
@ -231,19 +232,40 @@ namespace NavScm.NavInterface
|
|||||||
}
|
}
|
||||||
Directory.Delete(tempPath);
|
Directory.Delete(tempPath);
|
||||||
|
|
||||||
|
Contract.Assert(!Directory.Exists(tempPath));
|
||||||
|
Contract.Assume(process.HasExited);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exports a given NAV object to disk.
|
/// Exports a given NAV object to disk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>The file name must end with .txt, as finsql.exe deduces the export format from the destiation files
|
||||||
|
/// extension (crap). We have no other option here as to play by these rules.</para>
|
||||||
|
/// <para>Be aware, that NAV uses some strange mix of CP850 and CP1252 to encode the text files,
|
||||||
|
/// this is mean stuff here. The call does not try to convert this into something more sensible
|
||||||
|
/// at this point, especially since the IDE won't be able to handle this properly if you have to
|
||||||
|
/// work with the files manually. Checked with NAV 2015, YMMV.</para></remarks>
|
||||||
|
/// <para>Check http://forum.mibuso.com/discussion/37078/encoding-of-exported-navision-objects-txt-files
|
||||||
|
/// for further details about this.</para>
|
||||||
/// <param name="obj">The NAV object as taken from the SQL database or from the cache (doesn't matter).</param>
|
/// <param name="obj">The NAV object as taken from the SQL database or from the cache (doesn't matter).</param>
|
||||||
/// <param name="destinationFileName">The name of the destination file. The system ensures, that the file
|
/// <param name="destinationFileName">The name of the destination file. The file name must end with .txt.</param>
|
||||||
/// ends with .txt, as finsql.exe deduces the export format from the destiation files extension (crap).</param>
|
|
||||||
public void Export(NavObject obj, string destinationFileName)
|
public void Export(NavObject obj, string destinationFileName)
|
||||||
{
|
{
|
||||||
Contract.Requires(obj != null);
|
Contract.Requires(obj != null);
|
||||||
Contract.Requires(destinationFileName != "");
|
Contract.Requires(destinationFileName != "");
|
||||||
|
Contract.Requires(Path.GetExtension(destinationFileName) == ".txt");
|
||||||
|
|
||||||
|
// TODO: Skip Unlicensed objects?
|
||||||
|
string command = $"Command=ExportObjects,File=\"{destinationFileName}\",Filter=\"{obj.GetFilter()}\"";
|
||||||
|
log.DebugFormat("Export: Build command string: {0}", command);
|
||||||
|
var result = ExecuteCommand(command);
|
||||||
|
if (! result.Success)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Cannot export object {obj.NavType} ID {obj.ID}: {result.ErrorMessage}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ namespace NavScm.NavInterface
|
|||||||
partial void OnLoaded()
|
partial void OnLoaded()
|
||||||
{
|
{
|
||||||
Contract.Requires(Company_Name.Length == 0);
|
Contract.Requires(Company_Name.Length == 0);
|
||||||
Contract.Requires(Type >= 0 && Type <= 9 && Type != 2 && Type != 4 );
|
Contract.Requires(Type >= 1 && Type <= 9 && Type != 2 && Type != 4 );
|
||||||
/*
|
/*
|
||||||
if (Company_Name.Length > 0)
|
if (Company_Name.Length > 0)
|
||||||
throw new InvalidOperationException($"The object {CacheKey} holds a variant with the company name {Company_Name}, which is unsupported");
|
throw new InvalidOperationException($"The object {CacheKey} holds a variant with the company name {Company_Name}, which is unsupported");
|
||||||
@ -93,6 +93,27 @@ namespace NavScm.NavInterface
|
|||||||
get { return Date.Add(Time.TimeOfDay); }
|
get { return Date.Add(Time.TimeOfDay); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a filter string suitable to filter the NAV Object table during finsql operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Filter-String usable f.x. in ExportObjects.</returns>
|
||||||
|
[Pure]
|
||||||
|
public string GetFilter()
|
||||||
|
{
|
||||||
|
switch(NavType)
|
||||||
|
{
|
||||||
|
case NavObjectType.Codeunit: return $"Type=Codeunit;ID={ID}";
|
||||||
|
case NavObjectType.MenuSuite: return $"Type=MenuSuite;ID={ID}";
|
||||||
|
case NavObjectType.Page: return $"Type=Page;ID={ID}";
|
||||||
|
case NavObjectType.Query: return $"Type=Query;ID={ID}";
|
||||||
|
case NavObjectType.Report: return $"Type=Report;ID={ID}";
|
||||||
|
case NavObjectType.Table: return $"Type=Table;ID={ID}";
|
||||||
|
case NavObjectType.XmlPort: return $"Type=XmlPort;ID={ID}";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"The Type {Type} is unknown, cannot convert to filter.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a hash key based on Type and ID.
|
/// Constructs a hash key based on Type and ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Loading…
Reference in New Issue
Block a user