Pārlūkot izejas kodu

Modified Migration2 to simplify log initialisation.
Improved check to see if logs need to be downloaded if the database is recreated.
Split log synchronisation into separate log download and upload threads.
Switch from lastBatchId to minBatchId for /api/batches/ call.

Brett Credo 8 gadi atpakaļ
vecāks
revīzija
fbe8ba0536

+ 1 - 1
BulkPrinting/BulkPrinting/BatchForm.cs

@@ -318,7 +318,7 @@ namespace BulkPrinting
318 318
                                 Log.Debug("Querying batches");
319 319
 
320 320
                                 Page<Batch> BatchPage = new Page<Batch>();
321
-                                if (Utility.RESTRequest(ref BatchPage, String.Format("/api/batches/?lastBatchId={0}", lastSyncedBatchId)))
321
+                                if (Utility.RESTRequest(ref BatchPage, String.Format("/api/batches/?minBatchId={0}", lastSyncedBatchId + 1)))
322 322
                                 {
323 323
                                     if (BatchPage.Items.Count == 0)
324 324
                                     {

+ 7 - 3
BulkPrinting/BulkPrinting/Globals.cs

@@ -26,8 +26,12 @@ namespace BulkPrinting
26 26
         public static Printer MaxPrinter;
27 27
         public static List<int> OpenBatches;
28 28
         public static UserLimits UserCurrentUsage;
29
-        public static EventWaitHandle LogSyncThreadEvent;
30
-        public static Thread LogSyncThread;
31
-        public static bool LogSyncThreadAborted;
29
+        public static Thread LogUploadThread;
30
+        public static object LogUploadThreadLock = new object();
31
+        public static bool LogUploadThreadCancelled;
32
+        public static bool UploadNewLogs;
33
+        public static Thread LogDownloadThread;
34
+        public static object LogDownloadThreadLock = new object();
35
+        public static bool LogDownloadThreadCancelled;
32 36
     }
33 37
 }

+ 2 - 0
BulkPrinting/BulkPrinting/Migrations.cs

@@ -147,6 +147,8 @@ namespace BulkPrinting
147 147
                 "UPDATE Parameters SET Value='2' WHERE Key='Migration'",
148 148
                 "UPDATE Batch SET Downloaded=(ReadyForDownload=1 AND DeliveredQuantity<=COALESCE((SELECT COUNT(*) FROM Voucher WHERE Voucher.BatchId=Batch.Id), 0))",
149 149
                 "INSERT INTO Parameters (Key, Value) VALUES ('LastSyncedBatchId', '0')",
150
+                "INSERT INTO Parameters (Key, Value) VALUES ('SyncBackwardsFromLogId', '0')",
151
+                "INSERT INTO Parameters (Key, Value) VALUES ('LoggingInitialised', '" + false.ToString() + "')",
150 152
                 "CREATE TABLE Logs_New (" +
151 153
                     "Id INTEGER PRIMARY KEY AUTOINCREMENT," +
152 154
                     "UserId INT NOT NULL," +

+ 3 - 2
BulkPrinting/BulkPrinting/UserLoginForm.cs

@@ -121,8 +121,9 @@ namespace BulkPrinting
121 121
                             Globals.DBConnection = Utility.OpenDBConnection();
122 122
                         }
123 123
                         Migrations.CheckMigrations(Globals.DBConnection);
124
-                        Utility.CheckLastSyncedLogID(Globals.DBConnection);
125
-                        Utility.TriggerLogSync(); //Perform log sync before any logging happens to ensure synchronicity with server
124
+                        Utility.CheckLogSynchronisation(Globals.DBConnection);
125
+                        Utility.TriggerLogDownload();
126
+                        Utility.TriggerLogUpload();
126 127
                     }
127 128
                 }
128 129
             }

+ 228 - 101
BulkPrinting/BulkPrinting/Utility.cs

@@ -391,7 +391,12 @@ namespace BulkPrinting
391 391
 
392 392
         public static int GetSavedParameterAsInt(SQLiteConnection conn, string key)
393 393
         {
394
-            return int.Parse(GetSavedParameter(conn, key));
394
+            return Convert.ToInt32(GetSavedParameter(conn, key));
395
+        }
396
+
397
+        public static bool GetSavedParameterAsBoolean(SQLiteConnection conn, string key)
398
+        {
399
+            return Convert.ToBoolean(GetSavedParameter(conn, key));
395 400
         }
396 401
 
397 402
         public static bool UpdateSavedParameter(SQLiteConnection conn, string key, object value)
@@ -399,7 +404,7 @@ namespace BulkPrinting
399 404
             return DBExecuteNonQuery(conn,
400 405
                 "UPDATE Parameters SET Value=@Value WHERE Key=@Key",
401 406
                 new SQLiteParameter("@Key", key),
402
-                new SQLiteParameter("@Value", value)) > 0;
407
+                new SQLiteParameter("@Value", Convert.ToString(value))) > 0;
403 408
         }
404 409
 
405 410
         public static void PrintVouchers(SQLiteConnection conn, int BatchId, int StartSeqNo, int EndSeqNo)
@@ -560,17 +565,23 @@ namespace BulkPrinting
560 565
                     new SQLiteParameter("@key", "SessionDataJson"),
561 566
                     new SQLiteParameter("@value", SessionDataJson));
562 567
                 LogEvent(Globals.DBConnection, VendorEvent.VendorEventType.Logout);
563
-                if (Globals.LogSyncThread != null)
568
+
569
+                CancelLogUploadWorker();
570
+                CancelLogDownloadWorker();
571
+
572
+                if (Globals.LogUploadThread != null)
564 573
                 {
565
-                    Globals.LogSyncThreadAborted = true;
566
-                    Globals.LogSyncThreadEvent.Set();
567
-                    if (Globals.LogSyncThread.IsAlive)
568
-                    {
569
-                        Globals.LogSyncThread.Join();
570
-                    }
571
-                    Globals.LogSyncThread = null;
574
+                    Globals.LogUploadThread.Join();
575
+                    Globals.LogUploadThread = null;
576
+                }
577
+                if (Globals.LogDownloadThread != null)
578
+                {
579
+                    Globals.LogDownloadThread.Join();
580
+                    Globals.LogDownloadThread = null;
572 581
                 }
573 582
 
583
+                Globals.UploadNewLogs = false;
584
+
574 585
                 Globals.DBConnection.Close();
575 586
                 Globals.SessionData = null;
576 587
                 Globals.SessionDatabasePassword = null;
@@ -592,146 +603,262 @@ namespace BulkPrinting
592 603
             return (int)MetaData.LastVendorEventRemoteId;
593 604
         }
594 605
 
595
-        public static void CheckLastSyncedLogID(SQLiteConnection conn)
606
+        public static void CheckLogSynchronisation(SQLiteConnection conn)
596 607
         {
597
-            int lastSyncedLogId = GetLastSyncedLogID();
598
-            if (lastSyncedLogId == 0)
608
+            if (!GetSavedParameterAsBoolean(conn, "LoggingInitialised"))
599 609
             {
600
-                return;
610
+                var lastSyncedLogId = GetLastSyncedLogID();
611
+
612
+                // If we already have logs then there is no need to synchronise.
613
+                // NOTE: logs are synchronised in reverse from SyncBackwardsFromLogId downwards
614
+                if ((long)DBExecuteScalar(conn, "SELECT COUNT(*) FROM Logs") == 0)
615
+                {
616
+                    UpdateSavedParameter(conn, "SyncBackwardsFromLogId", lastSyncedLogId);
617
+                }
618
+
619
+                // If there are logs on the server then ensure that the log autoincrement value is set to the last
620
+                // log ID received by the server so new local logs don't clash with existing log entries on the server.
621
+                if (lastSyncedLogId != 0)
622
+                {
623
+                    lock (Globals.DBWriteLock)
624
+                    {
625
+                        using (var trans = conn.BeginTransaction())
626
+                        {
627
+                            using (var command = new SQLiteCommand(conn))
628
+                            {
629
+                                command.Transaction = trans;
630
+                                command.Parameters.AddWithValue("@Id", lastSyncedLogId);
631
+
632
+                                command.CommandText = "UPDATE sqlite_sequence SET seq=CASE WHEN IFNULL(seq, 0)<@Id THEN @Id ELSE seq END WHERE name='Logs'";
633
+                                if (command.ExecuteNonQuery() == 0)
634
+                                {
635
+                                    command.CommandText = "INSERT INTO sqlite_sequence (name, seq) VALUES ('Logs', @id)";
636
+                                    command.ExecuteNonQuery();
637
+                                }
638
+                            }
639
+                            trans.Commit();
640
+                        }
641
+                    }
642
+                }
643
+
644
+                UpdateSavedParameter(conn, "LoggingInitialised", true);
601 645
             }
602
-            lock (Globals.DBWriteLock)
646
+        }
647
+
648
+        public static void CancelLogDownloadWorker()
649
+        {
650
+            lock (Globals.LogDownloadThreadLock)
603 651
             {
604
-                using (var trans = conn.BeginTransaction())
652
+                Globals.LogDownloadThreadCancelled = true;
653
+                Monitor.Pulse(Globals.LogDownloadThreadLock);
654
+            }
655
+        }
656
+
657
+        public static void LogDownloadWorker()
658
+        {
659
+            const int vendorEventPageSize = 1000;
660
+            const int retryInterval = 30000;
661
+
662
+            var conn = Globals.DBConnection;
663
+            //using (var conn = OpenDBConnection())
664
+            {
665
+                bool cancelled = false;
666
+                int maxRemoteId = GetSavedParameterAsInt(conn, "SyncBackwardsFromLogId");
667
+                while (!cancelled && (maxRemoteId > 0))
605 668
                 {
606
-                    using (var command = new SQLiteCommand(conn))
669
+                    var EventPage = new Page<VendorEvent>();
670
+                    if (RESTRequest(ref EventPage, String.Format("/api/vendorevents/?maxRemoteId={0}&pageSize={1}", maxRemoteId, vendorEventPageSize)))
607 671
                     {
608
-                        command.Transaction = trans;
609
-                        command.Parameters.AddWithValue("@Id", lastSyncedLogId);
672
+                        lock (Globals.DBWriteLock)
673
+                        {
674
+                            using (var trans = conn.BeginTransaction())
675
+                            {
676
+                                using (var insertCommand = new SQLiteCommand(
677
+                                    "INSERT INTO Logs (Id,  UserId,  VoucherId,  EventDate,  EventType,  Retry) " +
678
+                                              "VALUES (@id, @userid, @voucherid, @eventdate, @eventtype, @retry)",
679
+                                    conn, trans))
680
+                                using (var updateCommand = new SQLiteCommand(
681
+                                    "UPDATE Parameters SET Value=@Value WHERE Key='SyncBackwardsFromLogId'",
682
+                                    conn, trans))
683
+                                {
684
+                                    foreach (VendorEvent Event in EventPage.Items)
685
+                                    {
686
+                                        if (!Event.RemoteId.HasValue)
687
+                                        {
688
+                                            throw new Exception("Downloaded event with no remote ID");
689
+                                        }
690
+                                        else if (Event.RemoteId.Value > maxRemoteId)
691
+                                        {
692
+                                            throw new Exception("Downloaded event remote ID was not in descending order");
693
+                                        }
610 694
 
611
-                        command.CommandText = "UPDATE sqlite_sequence SET seq=CASE WHEN IFNULL(seq, 0)<@Id THEN @Id ELSE seq END WHERE name='Logs'";
612
-                        if (command.ExecuteNonQuery() == 0)
695
+                                        insertCommand.Parameters.Clear();
696
+                                        insertCommand.Parameters.AddWithValue("@id", Event.RemoteId);
697
+                                        insertCommand.Parameters.AddWithValue("@userid", Event.UserId);
698
+                                        insertCommand.Parameters.AddWithValue("@voucherid", Event.VoucherId);
699
+                                        insertCommand.Parameters.AddWithValue("@eventdate", Event.EventDate.UtcDateTime);
700
+                                        insertCommand.Parameters.AddWithValue("@eventtype", Event.EventType);
701
+                                        insertCommand.Parameters.AddWithValue("@retry", Event.Retry);
702
+                                        insertCommand.ExecuteNonQuery();
703
+
704
+                                        maxRemoteId = Event.RemoteId.Value - 1;
705
+                                    }
706
+
707
+                                    updateCommand.Parameters.Clear();
708
+                                    updateCommand.Parameters.AddWithValue("@Value", maxRemoteId.ToString());
709
+                                    updateCommand.ExecuteNonQuery();
710
+                                }
711
+
712
+                                trans.Commit();
713
+                            }
714
+                        }
715
+
716
+                        lock (Globals.LogDownloadThreadLock)
613 717
                         {
614
-                            command.CommandText = "INSERT INTO sqlite_sequence (name, seq) VALUES ('Logs', @id)";
615
-                            command.ExecuteNonQuery();
718
+                            cancelled = Globals.LogDownloadThreadCancelled;
719
+                        }
720
+                    }
721
+                    else
722
+                    {
723
+                        lock (Globals.LogDownloadThreadLock)
724
+                        {
725
+                            if (!Globals.LogDownloadThreadCancelled)
726
+                            {
727
+                                Monitor.Wait(Globals.LogDownloadThreadLock, retryInterval);
728
+                            }
729
+                            cancelled = Globals.LogDownloadThreadCancelled;
616 730
                         }
617 731
                     }
618
-                    trans.Commit();
619 732
                 }
620 733
             }
621 734
         }
622 735
 
623
-        public static bool SyncLogsWorker()
736
+        public static void TriggerLogDownload()
624 737
         {
738
+            if (Globals.LogDownloadThread == null)
739
+            {
740
+                Globals.LogDownloadThread = new Thread(() => LogDownloadWorker());
741
+                Globals.LogDownloadThread.Start();
742
+            }
743
+        }
744
+
745
+        public static void CancelLogUploadWorker()
746
+        {
747
+            lock (Globals.LogUploadThreadLock)
748
+            {
749
+                Globals.LogUploadThreadCancelled = true;
750
+                Monitor.Pulse(Globals.LogUploadThreadLock);
751
+            }
752
+        }
753
+
754
+        public static void LogUploadWorker()
755
+        {
756
+            const int uploadPageSize = 1000;
757
+            var retryInterval = 30000;
758
+
625 759
             var conn = Globals.DBConnection;
626 760
             //using (var conn = OpenDBConnection())
627 761
             {
628
-                while (Globals.LogSyncThreadEvent.WaitOne() && !Globals.LogSyncThreadAborted)
762
+                var cancelled = false;
763
+                while (! cancelled)
629 764
                 {
630
-                    int LastSyncedLogID = GetLastSyncedLogID();
765
+                    var uploadFailed = false;
766
+                    int lastSyncedLogId = GetLastSyncedLogID();
631 767
 
632 768
                     var result = DBExecuteScalar(conn, "SELECT MAX(Id) FROM Logs")?.ToString() ?? "0";
633
-                    if (string.IsNullOrWhiteSpace(result)) result = "0";
634
-
635
-                    int LastRecordedLogId = int.Parse(result.ToString());
636
-                    if (LastRecordedLogId > LastSyncedLogID)
637
-                    { //If local logs are newer than server logs
638
-                        List<RemoteVendorEvent> EventList = new List<RemoteVendorEvent>();
639
-                        RemoteVendorEvent NextEvent;
640
-                        RemoteVendorEventResponse LastVendorEventIdSynced;
641
-                        int Counter = 0;
769
+                    if (string.IsNullOrWhiteSpace(result))
770
+                    {
771
+                        result = "0";
772
+                    }
773
+                    int lastRecordedLogId = int.Parse(result.ToString());
774
+
775
+                    if (lastRecordedLogId > lastSyncedLogId)
776
+                    {
777
+                        List<RemoteVendorEvent> eventList = new List<RemoteVendorEvent>();
778
+                        int counter = 0;
642 779
                         using (var Command = new SQLiteCommand("Select Id,UserId,VoucherId,EventDate,EventType From Logs WHERE Id > @id", conn))
643 780
                         {
644
-                            Command.Parameters.AddWithValue("@id", LastSyncedLogID);
781
+                            Command.Parameters.AddWithValue("@id", lastSyncedLogId);
645 782
                             using (SQLiteDataReader read = Command.ExecuteReader())
646 783
                             {
647 784
                                 while (read.Read())
648 785
                                 {
649
-                                    Counter += 1;
650
-                                    NextEvent = new RemoteVendorEvent();
651
-                                    NextEvent.Id = read.GetInt32(0);
652
-                                    NextEvent.UserId = read.GetInt32(1);
786
+                                    counter += 1;
787
+                                    var nextEvent = new RemoteVendorEvent();
788
+                                    nextEvent.Id = read.GetInt32(0);
789
+                                    nextEvent.UserId = read.GetInt32(1);
653 790
                                     if (read.IsDBNull(2) == true)
654 791
                                     {
655
-                                        NextEvent.VoucherId = null;
792
+                                        nextEvent.VoucherId = null;
656 793
                                     }
657 794
                                     else
658 795
                                     {
659
-                                        NextEvent.VoucherId = read.GetInt32(2);
796
+                                        nextEvent.VoucherId = read.GetInt32(2);
660 797
                                     }
661
-                                    NextEvent.EventDate = (DateTime)read.GetDateTime(3);
662
-                                    NextEvent.EventType = (VendorEvent.VendorEventType)Enum.Parse(typeof(VendorEvent.VendorEventType), read.GetValue(4).ToString());
663
-                                    NextEvent.VendorId = Globals.SessionData.Credentials.Payload.Vendor.id;
664
-                                    EventList.Add(NextEvent);
665
-                                    if (Counter == 1000)
666
-                                    { //Send logs in pages of 1000
667
-                                        LastVendorEventIdSynced = new RemoteVendorEventResponse();
668
-                                        bool EventPostResult = RESTRequest<List<RemoteVendorEvent>, RemoteVendorEventResponse>(EventList, ref LastVendorEventIdSynced, "/api/vendorevents/");
669
-                                        Counter = 0;
670
-                                        EventList = new List<RemoteVendorEvent>();
798
+                                    nextEvent.EventDate = read.GetDateTime(3);
799
+                                    nextEvent.EventType = (VendorEvent.VendorEventType)Enum.Parse(typeof(VendorEvent.VendorEventType), read.GetValue(4).ToString());
800
+                                    nextEvent.VendorId = Globals.SessionData.Credentials.Payload.Vendor.id;
801
+                                    eventList.Add(nextEvent);
802
+
803
+                                    if (counter == uploadPageSize)
804
+                                    {
805
+                                        var response = new RemoteVendorEventResponse();
806
+                                        if (RESTRequest(eventList, ref response, "/api/vendorevents/"))
807
+                                        {
808
+                                            counter = 0;
809
+                                            eventList.Clear();
810
+                                        }
811
+                                        else
812
+                                        {
813
+                                            uploadFailed = true;
814
+                                            break;
815
+                                        }
671 816
                                     }
672 817
                                 }
673
-                                if (Counter > 0)
674
-                                { //Left over logs not synced in a 100 item page
675
-                                    LastVendorEventIdSynced = new RemoteVendorEventResponse();
676
-                                    bool EventPostResult = RESTRequest<List<RemoteVendorEvent>, RemoteVendorEventResponse>(EventList, ref LastVendorEventIdSynced, "/api/vendorevents/");
818
+                                if (counter > 0)
819
+                                {
820
+                                    var response = new RemoteVendorEventResponse();
821
+                                    if (! RESTRequest(eventList, ref response, "/api/vendorevents/"))
822
+                                    {
823
+                                        uploadFailed = true;
824
+                                    }
677 825
                                 }
678 826
                             }
679 827
                         }
680 828
                     }
681
-                    else if (LastSyncedLogID > LastRecordedLogId)
682
-                    { //Server logs are newer than local logs, indicating loss of local database - resync entire table
683
-                        DBExecuteNonQuery(conn, "DELETE FROM Logs");
684
-                        Page<VendorEvent> EventPage;
685
-                        int TotalPages = 1;
686
-                        for (int PageNumber = 1; PageNumber <= TotalPages; PageNumber++)
829
+
830
+                    lock (Globals.LogUploadThreadLock)
831
+                    {
832
+                        while (! Globals.LogUploadThreadCancelled && ! Globals.UploadNewLogs)
687 833
                         {
688
-                            EventPage = new Page<VendorEvent>();
689
-                            bool EventGetResult = RESTRequest<Page<VendorEvent>>(ref EventPage, String.Format("/api/vendorevents/?page={0}&pagesize=1000", PageNumber));
690
-                            if (TotalPages == 1)
691
-                            { //Number of pages is sent with first page, so update on the fly
692
-                                TotalPages = EventPage.NumPages;
693
-                            }
694
-                            lock (Globals.DBWriteLock)
834
+                            Monitor.Wait(Globals.LogUploadThreadLock, uploadFailed ? retryInterval : Timeout.Infinite);
835
+                            if (uploadFailed)
695 836
                             {
696
-                                using (var trans = conn.BeginTransaction())
697
-                                {
698
-                                    var Sql = "INSERT INTO Logs (Id, UserId, VoucherId, EventDate, EventType, Retry) VALUES (@id, @userid, @voucherid, @eventdate, @eventtype, @retry)";
699
-                                    using (var cmd = new SQLiteCommand(Sql, conn, trans))
700
-                                    {
701
-                                        foreach (VendorEvent Event in EventPage.Items)
702
-                                        {
703
-                                            if (Event.RemoteId != null) //Only interested in downloading locally generated logs
704
-                                            {
705
-                                                cmd.Parameters.Clear();
706
-                                                cmd.Parameters.AddWithValue("@id", Event.RemoteId);
707
-                                                cmd.Parameters.AddWithValue("@userid", Event.UserId);
708
-                                                cmd.Parameters.AddWithValue("@voucherid", Event.VoucherId);
709
-                                                cmd.Parameters.AddWithValue("@eventdate", Event.EventDate.UtcDateTime);
710
-                                                cmd.Parameters.AddWithValue("@eventtype", Event.EventType);
711
-                                                cmd.Parameters.AddWithValue("@retry", Event.Retry);
712
-                                                cmd.ExecuteNonQuery();
713
-                                            }
714
-                                        }
715
-                                    }
716
-                                    trans.Commit();
717
-                                }
837
+                                break;
718 838
                             }
719 839
                         }
840
+                        Globals.UploadNewLogs = false;
841
+                        cancelled = Globals.LogUploadThreadCancelled;
720 842
                     }
721 843
                 }
722 844
             }
723
-            return true;
724 845
         }
725 846
 
726
-        public static void TriggerLogSync()
847
+        public static void TriggerLogUpload()
727 848
         {
728
-            if (Globals.LogSyncThread == null)
849
+            if (Globals.LogUploadThread == null)
850
+            {
851
+                Globals.LogUploadThread = new Thread(() => LogUploadWorker());
852
+                Globals.LogUploadThread.Start();
853
+            }
854
+            else
729 855
             {
730
-                Globals.LogSyncThreadEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
731
-                Globals.LogSyncThread = new Thread(() => SyncLogsWorker());
732
-                Globals.LogSyncThread.Start();
856
+                lock (Globals.LogUploadThreadLock)
857
+                {
858
+                    Globals.UploadNewLogs = true;
859
+                    Monitor.Pulse(Globals.LogUploadThreadLock);
860
+                }
733 861
             }
734
-            Globals.LogSyncThreadEvent.Set();
735 862
         }
736 863
 
737 864
         public static void LogBulkEvents(SQLiteConnection conn, List<EventLog> EventLogs)
@@ -761,7 +888,7 @@ namespace BulkPrinting
761 888
 
762 889
             if (Globals.SessionMode == SessionModes.Online)
763 890
             {
764
-                TriggerLogSync();
891
+                TriggerLogUpload();
765 892
             }
766 893
         }
767 894
 
@@ -777,7 +904,7 @@ namespace BulkPrinting
777 904
                 new SQLiteParameter("@retry", Retry));
778 905
             if (Globals.SessionMode == SessionModes.Online)
779 906
             {
780
-                TriggerLogSync();
907
+                TriggerLogUpload();
781 908
             }
782 909
         }
783 910