Browse Source

Form update script

Kenric Nugteren 1 năm trước cách đây
mục cha
commit
608978a69a

+ 1 - 0
prs.shared/Database Update Scripts/DatabaseUpdateScripts.cs

@@ -33,6 +33,7 @@ namespace PRS.Shared
             DataUpdater.RegisterUpdateScript<Update_7_24a>();
             DataUpdater.RegisterUpdateScript<Update_7_30>();
             DataUpdater.RegisterUpdateScript<Update_7_31>();
+            DataUpdater.RegisterUpdateScript<Update_7_34>();
         }
     }
 }

+ 385 - 0
prs.shared/Database Update Scripts/Update_7_34.cs

@@ -0,0 +1,385 @@
+using InABox.Clients;
+using InABox.Core;
+using InABox.Database;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Newtonsoft.Json.Linq;
+using NPOI.POIFS.FileSystem;
+using PRSClasses;
+using Syncfusion.Data.Extensions;
+using Syncfusion.DocIO.DLS;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Interop;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+
+namespace PRS.Shared
+{
+    public class Update_7_34 : DatabaseUpdateScript
+    {
+        public override VersionNumber Version => new VersionNumber(7, 34);
+
+        public override bool Update()
+        {
+            Logger.Send(LogType.Information, "", "Updating form data");
+            var fnc = typeof(Update_7_34).GetMethod("UpdateForm", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
+            FormUpdater.UpdateAllForms((_, vars) => vars.Count == 0, UpdateForm);
+            Logger.Send(LogType.Information, "", "Updating form data complete");
+            return true;
+        }
+
+        private Guid SaveDocument(byte[] data)
+        {
+            var id = Guid.NewGuid();
+            var document = new Document
+            {
+                FileName = $"{id}.formdocument",
+                Data = data
+            };
+            DbFactory.Provider.Save(document);
+            return document.ID;
+        }
+
+        private DFLayoutEmbeddedMediaValue ConvertImage(byte[] data)
+        {
+            var thumbnail = ImageUtils.BitmapImageFromBytes(data)?.Resize(200, 200).ToArray<BmpBitmapEncoder>();
+
+            return new DFLayoutEmbeddedMediaValue
+            {
+                ID = SaveDocument(data),
+                Thumbnail = thumbnail,
+                Data = data
+            };
+        }
+
+        private DFLayoutEmbeddedMediaValue ConvertVideo(byte[] data)
+        {
+            var thumbnail = ImageUtils.BitmapImageFromBytes(data)?.Resize(200, 200).ToArray<BmpBitmapEncoder>();
+            return new DFLayoutEmbeddedMediaValue
+            {
+                ID = SaveDocument(data),
+                Thumbnail = new Bitmap(256, 256).WatermarkImage("Video Data", Color.Gray).AsBitmapImage().ToArray<BmpBitmapEncoder>(),
+                Data = data
+            };
+        }
+
+        private DFLayoutLookupValue ConvertLookup(DigitalFormVariable variable, Guid lookupID)
+        {
+            if(lookupID == Guid.Empty)
+            {
+                return new DFLayoutLookupValue();
+            }
+            var properties = (variable.GetProperties() as DFLayoutLookupFieldProperties)!;
+
+            if (!CoreUtils.TryGetEntity(properties.LookupType, out var type))
+            {
+                Logger.Send(LogType.Error, "", $"Invalid lookup type {properties.LookupType}");
+                return new DFLayoutLookupValue { ID = lookupID };
+            }
+            var client = ClientFactory.CreateClient(type);
+            var columns = LookupFactory.DefineColumns(type);
+            foreach (var property in properties.AdditionalPropertiesList)
+            {
+                columns.Add(property);
+            }
+
+            var filter = Filter.Create(type, "ID").IsEqualTo(lookupID);
+
+            var row = client.Query(
+                filter,
+                columns,
+                LookupFactory.DefineSort(type)
+            ).Rows.FirstOrDefault();
+            if (row is null)
+            {
+                return new DFLayoutLookupValue { ID = lookupID };
+            }
+            var value = new DFLayoutLookupValue
+            {
+                ID = lookupID,
+                Values = row.ToDictionary(new[] { "ID" })
+            };
+            value.Text = LookupFactory.FormatLookup(type, value.Values, Enumerable.Empty<string>());
+            return value;
+        }
+
+        private object? Deserialize(DigitalFormVariable variable, Type fieldType, DFLoadStorage values)
+        {
+            var value = values.GetValue(variable.Code);
+            if (fieldType == typeof(DFLayoutBooleanField))
+            {
+                if (value is bool b)
+                {
+                    return b;
+                }
+                else if (bool.TryParse(value as string, out var result))
+                {
+                    return result;
+                }
+                else if (value is string str)
+                {
+                    var properties = (variable.GetProperties() as DFLayoutBooleanFieldProperties)!;
+                    if (str == properties.TrueValue)
+                        return true;
+                    if (str == properties.FalseValue)
+                        return false;
+                    throw new Exception($"Invalid boolean value {str}");
+                }
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if(fieldType == typeof(DFLayoutDateField))
+            {
+                if (value is DateTime date)
+                    return date;
+                if (DateTime.TryParseExact(value as string, "dd-MM-yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
+                    return result;
+                if (DateTime.TryParse(value as string, out result))
+                    return result;
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if (fieldType == typeof(DFLayoutDoubleField))
+            {
+                if (value is double d)
+                    return d;
+                if (double.TryParse(value as string, out var result))
+                    return result;
+                if(value is not null)
+                {
+                    throw new Exception($"Invalid double '{value}'");
+                }
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if (fieldType == typeof(DFLayoutEmbeddedImage))
+            {
+                if (value is byte[] b)
+                    return ConvertImage(b);
+                else if(value is string str)
+                {
+                    if (Guid.TryParse(str, out var id))
+                        return variable.Deserialize(values.GetEntry(variable.Code));
+                    try
+                    {
+                        var tuple = Serialization.Deserialize<Tuple<Guid, byte[]>>(str, true);
+                        if(tuple != null)
+                        {
+                            return new DFLayoutEmbeddedMediaValue
+                            {
+                                ID = tuple.Item1,
+                                Thumbnail = tuple.Item2,
+                            };
+                        }
+                    }
+                    catch
+                    {
+                    }
+                    try
+                    {
+                        return ConvertImage(System.Convert.FromBase64String(str));
+                    }
+                    catch(Exception e)
+                    {
+                        throw new Exception($"Error in image data; invalid Base-64: {e.Message}");
+                    }
+                }
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if (fieldType == typeof(DFLayoutIntegerField))
+            {
+                if (value is null)
+                    return variable.Deserialize(values.GetEntry(variable.Code));
+
+                if (value.GetType().IsNumeric())
+                {
+                    return System.Convert.ToInt32(value);
+                }
+                if (int.TryParse(value as string, out var result))
+                    return result;
+                if(value != null)
+                {
+                    throw new Exception($"Invalid integer '{value}'");
+                }
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if(fieldType == typeof(DFLayoutLookupField))
+            {
+                if(value is string str && Guid.TryParse(str, out var id))
+                {
+                    return ConvertLookup(variable, id);
+                }
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if(fieldType == typeof(DFLayoutMultiImage))
+            {
+                string[]? imgs;
+                try
+                {
+                    imgs = values.GetValue<string[]?>(variable.Code);
+                }
+                catch
+                {
+                    var data = values.GetValue<byte[]>(variable.Code);
+                    if(data != null)
+                    {
+                        var newValueList = new DFLayoutEmbeddedMediaValues();
+                        newValueList.Add(ConvertImage(data));
+                        return newValueList;
+                    }
+                    return variable.Deserialize(values.GetEntry(variable.Code));
+                }
+                if (imgs is null)
+                {
+                    return variable.Deserialize(values.GetEntry(variable.Code));
+                }
+
+                var valueList = new DFLayoutEmbeddedMediaValues();
+                foreach (string s in imgs)
+                {
+                    if (!s.IsNullOrWhiteSpace())
+                    {
+                        try
+                        {
+                            var externaldata = Serialization.Deserialize<(Guid, byte[])>(s, strict: true);
+                            if (externaldata.Item1 != Guid.Empty)
+                            {
+                                valueList.Add(new DFLayoutEmbeddedMediaValue()
+                                {
+                                    ID = externaldata.Item1,
+                                    Thumbnail = externaldata.Item2
+                                });
+                            }
+                        }
+                        catch
+                        {
+                            try
+                            {
+                                var data = System.Convert.FromBase64String(s);
+                                valueList.Add(ConvertImage(data));
+                            }
+                            catch
+                            {
+                                throw new Exception($"Could not convert multi-image");
+                            }
+                        }
+                    }
+                }
+                return valueList;
+            }
+            else if(fieldType == typeof(DFLayoutMultiSignaturePad))
+            {
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if(fieldType == typeof(DFLayoutOptionField))
+            {
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if (fieldType == typeof(DFLayoutSignaturePad))
+            {
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if (fieldType == typeof(DFLayoutStringField))
+            {
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if (fieldType == typeof(DFLayoutTimeField))
+            {
+                if (value is TimeSpan time)
+                    return time;
+                if (TimeSpan.TryParseExact(value as string, "c", CultureInfo.InvariantCulture, TimeSpanStyles.None, out var result))
+                    return result;
+                if (TimeSpan.TryParse(value as string, out result))
+                    return result;
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if (fieldType == typeof(DFLayoutVideoField))
+            {
+                if (value is byte[] b)
+                    return ConvertVideo(b);
+                else if (value is string str)
+                {
+                    if (Guid.TryParse(str, out var id))
+                        return variable.Deserialize(values.GetEntry(variable.Code));
+                    try
+                    {
+                        var tuple = Serialization.Deserialize<Tuple<Guid, byte[]>>(str, true);
+                        if (tuple != null)
+                        {
+                            return new DFLayoutEmbeddedMediaValue
+                            {
+                                ID = tuple.Item1,
+                                Thumbnail = tuple.Item2,
+                            };
+                        }
+                    }
+                    catch
+                    {
+                    }
+                    try
+                    {
+                        return ConvertVideo(System.Convert.FromBase64String(str));
+                    }
+                    catch (Exception e)
+                    {
+                        throw new Exception($"Error in video data; invalid Base-64: {e.Message}");
+                    }
+                }
+                return variable.Deserialize(values.GetEntry(variable.Code));
+            }
+            else if(fieldType == typeof(DFLayoutAddTaskField))
+            {
+                return value?.ToString();
+            }
+            else
+            {
+                throw new Exception($"Unhandled variable type {fieldType}")
+            }
+        }
+
+        private bool UpdateForm(Type formType, IDigitalFormInstance instance, DigitalForm form, IList<DigitalFormVariable> variables)
+        {
+            var values = DigitalForm.DeserializeFormData(instance);
+            if(values is null)
+            {
+                return false;
+            }
+
+            var save = new DFSaveStorage();
+            foreach(var variable in variables)
+            {
+                try
+                {
+                    var value = Deserialize(variable, variable.FieldType(), values);
+                    variable.Serialize(save.GetEntry(variable.Code), value);
+                }
+                catch(Exception e)
+                {
+                    Logger.Send(LogType.Error, "", $"Error in data for {variable.Code} ({instance.ID}): {e.Message}");
+                    save.FormData[variable.Code] = values.GetValue(variable.Code);
+                    foreach(var (k, v) in values.GetEntry(variable.Code).SubItems())
+                    {
+                        save.FormData[$"{variable.Code}.{k}"] = v;
+                    }
+                }
+            }
+
+            foreach(var (k, v) in values.Items())
+            {
+                if(!save.FormData.ContainsKey(k) && !k.Contains('.') && !variables.Any(x => x.Code == k))
+                {
+                    save.FormData[k] = v;
+                }
+            }
+
+            DigitalForm.SerializeFormData(instance, variables, save);
+            
+
+            return instance.IsChanged();
+        }
+    }
+}

+ 76 - 0
prs.shared/Database Update Scripts/Utils/FormUpdater.cs

@@ -0,0 +1,76 @@
+using InABox.Core;
+using InABox.Database;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Shared
+{
+    public static class FormUpdater
+    {
+        public static void UpdateAllForms(
+            Func<DigitalForm, IList<DigitalFormVariable>, bool> skip,
+            Func<Type, IDigitalFormInstance, DigitalForm, IList<DigitalFormVariable>, bool> convert, int step = 100)
+        {
+            var forms = DbFactory.Provider.Query<DigitalForm>(
+                null,
+                new Columns<DigitalForm>(x => x.ID)
+                    .Add(x => x.Code)
+                    .Add(x => x.Description)
+                    .Add(x => x.AppliesTo));
+            foreach(var form in forms.ToObjects<DigitalForm>())
+            {
+                Logger.Send(LogType.Information, "", $"Working on '{form.Code} - {form.Description}'");
+                var variables = DbFactory.Provider.Query(
+                    new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(form.ID),
+                    null).ToObjects<DigitalFormVariable>().ToArray();
+                if(skip(form, variables))
+                {
+                    continue;
+                }
+
+                var formType = DFUtils.GetFormInstanceType(form.AppliesTo);
+                if(formType is null)
+                {
+                    Logger.Send(LogType.Error, "", $"{form.AppliesTo} does not refer to a digital form instance class.");
+                    continue;
+                }
+
+                var IDs = DbFactory.Provider.Query(formType,
+                    Filter.Create<IDigitalFormInstance>(formType, x => x.Form.ID).IsEqualTo(form.ID),
+                    Columns.Create<IDigitalFormInstance>(formType)
+                        .Add<IDigitalFormInstance>(x => x.ID)).Rows.Select(x => x.Get<Guid>("ID")).ToArray();
+
+                for(int i = 0; i < IDs.Length; i += step)
+                {
+                    int j = Math.Min(i + step, IDs.Length);
+                    var ids = IDs[i..j];
+
+                    Logger.Send(LogType.Information, "", $"Converting {j - i} items; {IDs.Length - j} left.");
+                    var formInstances = DbFactory.Provider.Query(formType,
+                        Filter.Create<IDigitalFormInstance>(formType, x => x.ID).InList(ids),
+                        Columns.Create<IDigitalFormInstance>(formType)
+                            .Add<IDigitalFormInstance>(x => x.ID)
+                            .Add<IDigitalFormInstance>(x => x.FormData)
+                            .Add<IDigitalFormInstance>(x => x.BlobData));
+
+                    var save = new List<Entity>();
+                    foreach (var row in formInstances.Rows)
+                    {
+                        var formInstance = (row.ToObject(formType) as IDigitalFormInstance)!;
+                        if (convert(formType, formInstance, form, variables))
+                        {
+                            save.Add((formInstance as Entity)!);
+                        }
+                    }
+                    if(save.Count > 0)
+                    {
+                        DbFactory.Provider.Save(formType, save);
+                    }
+                }
+            }
+        }
+    }
+}