684 lines
25 KiB
C#
684 lines
25 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Threading.Tasks;
|
|
using Newtonsoft.Json.Linq;
|
|
using System.Security.Cryptography;
|
|
using biz = GZTW.AyaNova.BLL;
|
|
|
|
|
|
|
|
namespace AyaNova.PlugIn.V8
|
|
{
|
|
class util
|
|
{
|
|
public static string PRE_RELEASE_VERSION_STRING { get; set; }
|
|
|
|
public static GZTW.AyaNova.BLL.LocalizedTextTable LocaleText = null;
|
|
const string TEST_ROUTE = "notify/hello";
|
|
const string API_BASE_ROUTE = "api/v8/";
|
|
static HttpClient client = new HttpClient();
|
|
//url once known to be good
|
|
internal static string ApiBaseUrl { get; set; }
|
|
internal static string JWT { get; set; }
|
|
public static bool Initialized { get; set; }
|
|
|
|
|
|
|
|
public util()
|
|
{
|
|
Initialized = false;
|
|
JWT = string.Empty;
|
|
}
|
|
|
|
|
|
#region INIT / AUTH
|
|
private static void InitClient()
|
|
{
|
|
if (Initialized) return;
|
|
client.BaseAddress = new Uri(ApiBaseUrl);
|
|
client.DefaultRequestHeaders.Accept.Clear();
|
|
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
// client.DefaultRequestHeaders.Add("X-AY-Import-Mode", "true");
|
|
Initialized = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only a return value of "OK" is ok
|
|
/// </summary>
|
|
/// <param name="serverUrl"></param>
|
|
/// <returns></returns>
|
|
public static async Task<string> TestUrlAsync(string serverUrl)
|
|
{
|
|
if (string.IsNullOrEmpty(serverUrl)) return "Server url required";
|
|
|
|
if (!serverUrl.Contains("/api/"))
|
|
{
|
|
if (!serverUrl.EndsWith("/")) serverUrl += "/";
|
|
serverUrl += API_BASE_ROUTE;
|
|
}
|
|
|
|
if (!serverUrl.StartsWith("http")) serverUrl = "http://" + serverUrl;
|
|
|
|
//try to connect, ping the server api
|
|
if (!Initialized)
|
|
{
|
|
client.BaseAddress = new Uri(serverUrl);
|
|
client.DefaultRequestHeaders.Accept.Clear();
|
|
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
Initialized = true;
|
|
}
|
|
|
|
try
|
|
{
|
|
HttpResponseMessage response = await client.GetAsync(serverUrl + TEST_ROUTE);
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
ApiBaseUrl = serverUrl;
|
|
return "OK";
|
|
}
|
|
else
|
|
{
|
|
return "Failed: " + response.StatusCode.ToString();
|
|
}
|
|
}
|
|
catch { return "Failed"; }
|
|
|
|
|
|
// return "failed";
|
|
}
|
|
|
|
|
|
|
|
|
|
public async static Task<bool> AuthenticateAsync(string login, string password = null)
|
|
{
|
|
InitClient();
|
|
|
|
if (password == null)
|
|
password = login;
|
|
|
|
dynamic creds = new JObject();
|
|
creds.login = login;
|
|
creds.password = password;
|
|
|
|
ApiResponse a = await PostAsync("auth", creds.ToString());
|
|
|
|
if (a.HttpResponse.IsSuccessStatusCode)
|
|
{
|
|
JWT = a.ObjectResponse["data"]["token"].Value<string>();
|
|
//Must be *the* SuperUser to continue:
|
|
a = await GetAsync("user/amsu");
|
|
var IsSuperUser = a.ObjectResponse["data"].Value<bool>();
|
|
if (!IsSuperUser)
|
|
{
|
|
JWT = string.Empty;
|
|
return false;
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async static Task<ApiResponse> GetAsync(string route)
|
|
{
|
|
var requestMessage = new HttpRequestMessage(HttpMethod.Get, route);
|
|
requestMessage.Headers.Add("X-AY-Import-Mode", "1");
|
|
if (!string.IsNullOrWhiteSpace(JWT))
|
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);
|
|
|
|
HttpResponseMessage response = await client.SendAsync(requestMessage);
|
|
var responseAsString = await response.Content.ReadAsStringAsync();
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
throw new Exception("GET error, route: " + route + "\n" + responseAsString + "\n" + response.ReasonPhrase);
|
|
}
|
|
else
|
|
return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
|
|
}
|
|
|
|
//
|
|
public async static Task<ApiResponse> PostAsync(string route, string postJson = null)
|
|
{
|
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, route);
|
|
requestMessage.Headers.Add("X-AY-Import-Mode", "1");
|
|
if (!string.IsNullOrWhiteSpace(JWT))
|
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);
|
|
|
|
if (!string.IsNullOrWhiteSpace(postJson))
|
|
requestMessage.Content = new StringContent(postJson, System.Text.Encoding.UTF8, "application/json");
|
|
|
|
HttpResponseMessage response = await client.SendAsync(requestMessage);
|
|
var responseAsString = await response.Content.ReadAsStringAsync();
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
throw new Exception("POST error, route: " + route + "\n" + responseAsString + "\n" + response.ReasonPhrase);
|
|
}
|
|
else
|
|
return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
|
|
}
|
|
|
|
|
|
|
|
public async static Task<ApiResponse> PutAsync(string route, string putJson = null)
|
|
{
|
|
var requestMessage = new HttpRequestMessage(HttpMethod.Put, route);
|
|
requestMessage.Headers.Add("X-AY-Import-Mode", "1");
|
|
if (!string.IsNullOrWhiteSpace(JWT))
|
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);
|
|
|
|
if (!string.IsNullOrWhiteSpace(putJson))
|
|
requestMessage.Content = new StringContent(putJson, System.Text.Encoding.UTF8, "application/json");
|
|
|
|
HttpResponseMessage response = await client.SendAsync(requestMessage);
|
|
var responseAsString = await response.Content.ReadAsStringAsync();
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
throw new Exception("PUT error, route: " + route + "\n" + responseAsString + "\n" + response.ReasonPhrase);
|
|
}
|
|
else
|
|
return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
|
|
}
|
|
|
|
|
|
public async static Task<ApiResponse> PostFormDataAsync(string route, MultipartFormDataContent formContent)
|
|
{
|
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, route);
|
|
requestMessage.Headers.Add("X-AY-Import-Mode", "1");
|
|
if (!string.IsNullOrWhiteSpace(JWT))
|
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);
|
|
|
|
requestMessage.Content = formContent;
|
|
|
|
HttpResponseMessage response = await client.SendAsync(requestMessage);
|
|
var responseAsString = await response.Content.ReadAsStringAsync();
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
throw new Exception("POST FORMDATA error, route: " + route + "\n" + responseAsString + "\n" + response.ReasonPhrase);
|
|
}
|
|
else
|
|
return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
|
|
}
|
|
|
|
//eoc
|
|
public class ApiResponse
|
|
{
|
|
public HttpResponseMessage HttpResponse { get; set; }
|
|
public JObject ObjectResponse { get; set; }
|
|
public string CompactResponse
|
|
{
|
|
get
|
|
{
|
|
return ObjectResponse.ToString(Newtonsoft.Json.Formatting.None);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//make an event log entry to preserve the original v7 history such as it is
|
|
public async static Task EventLog(AyaType RavenType, long RavenId, long RavenCreatorId, long RavenModifierId, string sCreated, string sModified)
|
|
{
|
|
DateTime Created = DateTime.UtcNow;
|
|
if (!string.IsNullOrWhiteSpace(sCreated))
|
|
Created = DateTime.Parse(sCreated).ToUniversalTime();
|
|
|
|
DateTime Modified = DateTime.UtcNow;
|
|
if (!string.IsNullOrWhiteSpace(sCreated))
|
|
Modified = DateTime.Parse(sModified).ToUniversalTime();
|
|
|
|
dynamic d = new JObject();
|
|
d.ayType = (int)RavenType;
|
|
d.ayId = RavenId;
|
|
d.creator = RavenCreatorId;
|
|
d.modifier = RavenModifierId;
|
|
d.created = Created;
|
|
d.modified = Modified;
|
|
|
|
await PostAsync("event-log/v7", d.ToString());
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="jsonString"></param>
|
|
/// <returns></returns>
|
|
public static JObject Parse(string jsonString)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(jsonString))
|
|
{
|
|
return null;
|
|
}
|
|
return JObject.Parse(jsonString);
|
|
}
|
|
|
|
|
|
public static long IdFromResponse(ApiResponse a)
|
|
{
|
|
return a.ObjectResponse["data"]["id"].Value<long>();
|
|
}
|
|
|
|
public static uint CTokenFromResponse(ApiResponse a)
|
|
{
|
|
return a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
|
}
|
|
#endregion
|
|
|
|
#region Misc utils
|
|
|
|
//Used to ensure a unique name generated by appending -nnn is within length requirements by splitting and chopping part of text to keep name
|
|
public static string UniqueNameBuilder(string oldName, long appendValue, int maxLength)
|
|
{
|
|
//deadman switch
|
|
if (appendValue > int.MaxValue)
|
|
{
|
|
throw new System.OverflowException("UniqueNameBuilder: Unique name could not be generated for item \"" + oldName + "\" after " + int.MaxValue.ToString() + " attempts");
|
|
}
|
|
var appendString = "-" + appendValue.ToString();
|
|
string ret = oldName + appendString;
|
|
var diff = maxLength - ret.Length;
|
|
if (diff < 0)
|
|
{
|
|
ret = oldName.Substring(0, Math.Abs(diff)) + appendString;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//used to set nonsense values from imported user login and password
|
|
//just in case they are set active after import but never have their passwords set
|
|
//so they can't be as easily hacked into
|
|
public static string RandomString()
|
|
{
|
|
var b = new byte[32];
|
|
var random = RandomNumberGenerator.Create();
|
|
random.GetNonZeroBytes(b);
|
|
return Convert.ToBase64String(b);
|
|
}
|
|
|
|
public static string NormalizeTag(string inObj)
|
|
{
|
|
//Must be lowercase per rules
|
|
//This may be naive when we get international customers but for now supporting utf-8 and it appears it's safe to do this with unicode
|
|
inObj = inObj.ToLowerInvariant();
|
|
//No spaces in tags, replace with dashes
|
|
inObj = inObj.Replace(" ", "-");
|
|
//Remove multiple dash sequences
|
|
inObj = System.Text.RegularExpressions.Regex.Replace(inObj, "-+", "-");
|
|
//Ensure doesn't start or end with a dash
|
|
inObj = inObj.Trim('-');
|
|
//No longer than 255 characters
|
|
inObj = MaxLength(inObj, 255);
|
|
return inObj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Trim a string if necessary
|
|
/// </summary>
|
|
/// <param name="s"></param>
|
|
/// <param name="maxLength"></param>
|
|
/// <returns></returns>
|
|
public static string MaxLength(string s, int maxLength)
|
|
{
|
|
if (s.Length > maxLength)
|
|
s = s.Substring(0, maxLength);
|
|
return s;
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////
|
|
// v7 to v8 Date conversion
|
|
//NOTE: ensure null for empty date
|
|
//
|
|
public static string DateToV8(object o, bool neverEmpty = false) { return DateToV8(o.ToString(), neverEmpty); }
|
|
public static string DateToV8(string s, bool neverEmpty = false)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s))
|
|
{
|
|
if (neverEmpty)
|
|
{
|
|
return DateTime.UtcNow.ToString("o");
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
DateTime result = new DateTime();
|
|
if (DateTime.TryParse(s, out result))
|
|
return result.ToUniversalTime().ToString("o");
|
|
|
|
//couldn't be parsed
|
|
if (neverEmpty)
|
|
return DateTime.UtcNow.ToString("o");
|
|
return null;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// v7 to v8 Date conversion
|
|
//
|
|
//
|
|
public static bool DateIsPast(object o, bool neverEmpty = false) { return DateIsPast(o.ToString(), neverEmpty); }
|
|
public static bool DateIsPast(string s, bool neverEmpty = false)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s))
|
|
return false;//no date so not past
|
|
|
|
|
|
DateTime result = new DateTime();
|
|
if (DateTime.TryParse(s, out result))
|
|
return result < DateTime.Now;
|
|
//can't parse so no, it's not past
|
|
return false;
|
|
}
|
|
|
|
|
|
public enum AyaUiFieldDataType : int
|
|
{
|
|
NoType = 0,
|
|
DateTime = 1,
|
|
Date = 2,
|
|
Time = 3,
|
|
Text = 4,
|
|
Integer = 5,
|
|
Bool = 6,
|
|
Decimal = 7,
|
|
Currency = 8,
|
|
Tags = 9,
|
|
Enum = 10,
|
|
EmailAddress = 11
|
|
|
|
}
|
|
|
|
|
|
public static AyaType RootObjectToAyaType(biz.RootObjectTypes rot, biz.WorkorderTypes specificWoType = biz.WorkorderTypes.Unknown)
|
|
{
|
|
|
|
switch (rot)
|
|
{
|
|
case biz.RootObjectTypes.User:
|
|
return AyaType.User;
|
|
case biz.RootObjectTypes.Client:
|
|
return AyaType.Customer;
|
|
case biz.RootObjectTypes.Contract:
|
|
return AyaType.Contract;
|
|
case biz.RootObjectTypes.HeadOffice:
|
|
return AyaType.HeadOffice;
|
|
case biz.RootObjectTypes.LoanItem:
|
|
return AyaType.LoanUnit;
|
|
case biz.RootObjectTypes.Part:
|
|
return AyaType.Part;
|
|
case biz.RootObjectTypes.WorkorderPreventiveMaintenance:
|
|
return AyaType.PM;
|
|
case biz.RootObjectTypes.WorkorderItem:
|
|
switch (specificWoType)
|
|
{
|
|
case biz.WorkorderTypes.PreventiveMaintenance:
|
|
return AyaType.PMItem;
|
|
case biz.WorkorderTypes.Quote:
|
|
return AyaType.QuoteItem;
|
|
case biz.WorkorderTypes.Service:
|
|
return AyaType.WorkOrderItem;
|
|
default:
|
|
throw new NotImplementedException("V8:util:RootObjectToAyaType -> type " + rot.ToString() + "," + specificWoType.ToString() + " is not coded yet");
|
|
}
|
|
|
|
case biz.RootObjectTypes.Memo:
|
|
return AyaType.Memo;
|
|
case biz.RootObjectTypes.Project:
|
|
return AyaType.Project;
|
|
case biz.RootObjectTypes.PurchaseOrder:
|
|
return AyaType.PurchaseOrder;
|
|
case biz.RootObjectTypes.Unit:
|
|
return AyaType.Unit;
|
|
case biz.RootObjectTypes.UnitModel:
|
|
return AyaType.UnitModel;
|
|
case biz.RootObjectTypes.Vendor:
|
|
return AyaType.Vendor;
|
|
case biz.RootObjectTypes.WorkorderItemLabor:
|
|
return AyaType.WorkOrderItemLabor;
|
|
case biz.RootObjectTypes.WorkorderItemTravel:
|
|
return AyaType.WorkOrderItemTravel;
|
|
|
|
default:
|
|
throw new NotImplementedException("V8:util:RootObjectToAyaType -> type " + rot.ToString() + " is not coded yet");
|
|
}
|
|
}
|
|
|
|
public enum AyaType : int
|
|
{
|
|
//COREBIZOBJECT attribute must be set on objects that are:
|
|
//Attachable objects can have attachments,
|
|
//wikiable objects can have a wiki
|
|
//reviewable objects can have a review which is basically the same as a Reminder but with an object attached (was follow up schedmarker in v7)
|
|
//PIckList-able (has picklist template)
|
|
//Pretty much everything that represents some kind of real world object is wikiable or attachable as long as it has an ID and a type
|
|
//exceptions would be utility type objects like datalistview, formcustom etc that are not
|
|
|
|
//NOTE: NEW CORE OBJECTS - All areas of server AND CLIENT code that require adding any new core objects have been tagged with the following comment:
|
|
//CoreBizObject add here
|
|
//Search for that IN SERVER AND CLIENT CODE and you will see all areas that need coding for the new object
|
|
|
|
//***IMPORTANT: Also need to add translations for any new biz objects added that don't match exactly the name here in the key
|
|
//because enumlist gets it that way, i.e. "Global" would be the expected key
|
|
|
|
NoType = 0,
|
|
Global = 1,
|
|
//corebizobject
|
|
Widget = 2,
|
|
//corebizobject
|
|
User = 3,
|
|
ServerState = 4,
|
|
License = 5,
|
|
LogFile = 6,
|
|
PickListTemplate = 7,
|
|
//corebizobject
|
|
Customer = 8,
|
|
ServerJob = 9,
|
|
//corebizobject
|
|
Contract = 10,
|
|
TrialSeeder = 11,
|
|
ServerMetrics = 12,
|
|
Translation = 13,
|
|
UserOptions = 14,
|
|
//corebizobject
|
|
HeadOffice = 15,
|
|
//corebizobject
|
|
LoanUnit = 16,
|
|
FileAttachment = 17,
|
|
DataListView = 18,
|
|
FormCustom = 19,
|
|
//corebizobject
|
|
Part = 20,
|
|
//corebizobject
|
|
PM = 21,
|
|
//corebizobject
|
|
PMItem = 22,
|
|
//corebizobject
|
|
PMTemplate = 23,
|
|
//corebizobject
|
|
PMTemplateItem = 24,
|
|
//corebizobject
|
|
Project = 25,
|
|
//corebizobject
|
|
PurchaseOrder = 26,
|
|
//corebizobject
|
|
Quote = 27,
|
|
//corebizobject
|
|
QuoteItem = 28,
|
|
//corebizobject
|
|
QuoteTemplate = 29,
|
|
//corebizobject
|
|
QuoteTemplateItem = 30,
|
|
//corebizobject
|
|
Unit = 31,
|
|
//corebizobject
|
|
UnitModel = 32,
|
|
//corebizobject
|
|
Vendor = 33,
|
|
//--- WorkOrder
|
|
//corebizobject
|
|
WorkOrder = 34,
|
|
//corebizobject
|
|
WorkOrderItem = 35,
|
|
//corebizobject
|
|
WorkOrderItemExpense = 36,
|
|
//corebizobject
|
|
WorkOrderItemLabor = 37,
|
|
//corebizobject
|
|
WorkOrderItemLoan = 38,
|
|
//corebizobject
|
|
WorkOrderItemPart = 39,
|
|
//corebizobject
|
|
WorkOrderItemPartRequest = 40,
|
|
//corebizobject
|
|
WorkOrderItemScheduledUser = 41,
|
|
//corebizobject
|
|
WorkOrderItemTask = 42,
|
|
//corebizobject
|
|
WorkOrderItemTravel = 43,
|
|
//corebizobject
|
|
WorkOrderItemUnit = 44,
|
|
//---
|
|
//corebizobject
|
|
WorkOrderTemplate = 45,
|
|
//corebizobject
|
|
WorkOrderTemplateItem = 46,
|
|
GlobalOps = 47,//really only used for rights, not an object type of any kind
|
|
BizMetrics = 48,//deprecate? Not used for anything as of nov 2020
|
|
Backup = 49,
|
|
Notification = 50,
|
|
NotifySubscription = 51,
|
|
//corebizobject
|
|
Reminder = 52,
|
|
UnitMeterReading = 53,
|
|
CustomerServiceRequest = 54,
|
|
ServiceBank = 55,
|
|
OpsNotificationSettings = 56,
|
|
Report = 57,
|
|
DashboardView = 58,
|
|
//corebizobject
|
|
CustomerNote = 59,
|
|
//corebizobject
|
|
Memo = 60,
|
|
//corebizobject
|
|
Review = 61,
|
|
ServiceRate = 62,
|
|
TravelRate = 63,
|
|
TaxCode = 64
|
|
|
|
|
|
|
|
//NOTE: New objects added here need to also be added to the following classes:
|
|
//AyaNova.Biz.BizObjectExistsInDatabase
|
|
//AyaNova.Biz.BizObjectFactory
|
|
//AyaNova.Biz.BizRoles
|
|
//AyaNova.Biz.BizObjectNameFetcherDIRECT
|
|
//and in the CLIENT in ayatype.js
|
|
//and their model needs to have the ICoreBizObjectModel interface
|
|
|
|
//and need TRANSLATION KEYS because any type could show in the event log at the client end
|
|
|
|
}
|
|
|
|
|
|
|
|
public static bool LocaleIsCustomized(string localeName, GZTW.AyaNova.BLL.LocalizedTextTable lt, ProgressForm progress)
|
|
{
|
|
//checksum locales
|
|
//if a recognized checksum then don't import because it means it wasn't modified from stock
|
|
/*
|
|
Stock fresh trial db (fb) values:
|
|
Locale Custom English is customized [signature: -2007218741] exporting
|
|
Locale Deutsch is customized [signature: -605335883] exporting
|
|
Locale English is customized [signature: -2007218741] exporting
|
|
Locale Español is customized [signature: -1164206951] exporting
|
|
Locale Français is customized [signature: -720441286] exporting
|
|
|
|
Scotts:
|
|
Locale Custom English is customized [signature: 117115560] exporting
|
|
Locale Deutsch is customized [signature: 772843262] exporting
|
|
Locale English is customized [signature: 1382500515] exporting
|
|
Locale Español is customized [signature: 438715542] exporting
|
|
Locale Français is customized [signature: 804154061] exporting
|
|
|
|
Do not use these keys in hash calculation which were added / modified during various schema updates
|
|
Global.Label.UseInventory.Description
|
|
Locale.Label.LocaleFile
|
|
UnitNameDisplayFormats.Label.ModelSerial
|
|
UnitNameDisplayFormats.Label.SerialModel
|
|
UnitNameDisplayFormats.Label.SerialModelVendor
|
|
UnitNameDisplayFormats.Label.VendorModelSerial
|
|
O.PartBin
|
|
*/
|
|
List<int> StockLocaleHashes = new List<int>();
|
|
StockLocaleHashes.Add(-605335883);
|
|
StockLocaleHashes.Add(-2007218741);
|
|
StockLocaleHashes.Add(-1164206951);
|
|
StockLocaleHashes.Add(-720441286);
|
|
|
|
//calculate hash
|
|
List<string> allStrings = new List<string>();
|
|
foreach (var entry in lt.LT)
|
|
{
|
|
if (entry.Key == "Global.Label.UseInventory.Description") continue;
|
|
if (entry.Key == "Locale.Label.LocaleFile") continue;
|
|
if (entry.Key == "UnitNameDisplayFormats.Label.ModelSerial") continue;
|
|
if (entry.Key == "UnitNameDisplayFormats.Label.SerialModel") continue;
|
|
if (entry.Key == "UnitNameDisplayFormats.Label.SerialModelVendor") continue;
|
|
if (entry.Key == "UnitNameDisplayFormats.Label.VendorModelSerial") continue;
|
|
if (entry.Key == "O.PartBin") continue;
|
|
if (entry.Key == "UI.Label.CurrentUserName") continue;
|
|
|
|
allStrings.Add(entry.Value);
|
|
}
|
|
int CurrentLocaleHash = GetOrderIndependentHashCode<string>(allStrings);
|
|
|
|
if (StockLocaleHashes.Contains(CurrentLocaleHash)) return false;
|
|
progress.Append("Locale " + localeName + " is customized [signature: " + CurrentLocaleHash.ToString() + "] exporting");
|
|
|
|
return true;
|
|
}
|
|
|
|
public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
|
|
{
|
|
int hash = 0;
|
|
foreach (T element in source)
|
|
{
|
|
hash = unchecked(hash +
|
|
EqualityComparer<T>.Default.GetHashCode(element));
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
|
|
public class NameIdItem
|
|
{
|
|
public long Id { get; set; }
|
|
public string Name { get; set; }
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
}//eoc
|
|
|
|
|
|
}//ens
|