Subversion Repository Public Repository

ConnectionOdbcSdk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
´╗┐using System.Collections.Generic;
using System.Diagnostics;
using System;
using System.Windows.Forms;
using System.Data;
using System.Net;


//This class handles all the connections to all the Unity Connection servers we may be dealing with.
//There's always at least one, of course, but then an array of potentially dozens of servers beyond that in the larger
//inter and intra network collection.

//The server name, location, login information, connection status, ODBC connection object etc... are maintained in instances
//of this class which end up being held in a variable length array: oConnectionServers.
//This class has it's own background worker for doing logins - this is a little awkward but we have to do this since the Informix
//ODBC client timeouts are very long and can't be adjusted so having blocking calls to log in to each server is brutal.  Doing them
//all on bckground threads is a little nicer, though still a bit of a pain.
[assembly: CLSCompliant(true)]
namespace Cisco.UnityConnection.OdbcSdk
{
    //used to keep track of the current ODBC connection status of this server
    public enum ConnectionStatus 
    {
        OffLine = 0, //default state - no login attempted yet
        LoginPending = 1,
        LoginFailed = 2,
        Connected = 3
    }
    
    /// <summary>
    /// Set of properties and methods for connecting to and getting version information from a remote Unity Connection server.
    /// </summary>
    public class ConnectionServerOdbc : IDisposable
    {

        #region Fields and Properties

        /// <summary>
        /// Time of the last time we called the large operations pause telling Connection to updating speech dictionary data for
        /// users as well as pausing networking replication during large data change operations.  We need to call this every 
        /// 10 minutes or so to keep Connection in this state while we're adding/editing objects in bulk
        /// </summary>
        public DateTime TimeOfLastLargeOperationsPause { get; private set; }

        //instance of the version information of the Connection server we're attached to
        private readonly UnityVersionDetails _unityVersion = new UnityVersionDetails();

        public bool DebugMode { get; set; }

        public string UnityVersionString
        {
            get
            {
                return _unityVersion.ToString();
            }
        }

        public int UnityVersionMajor
        {
            get
            {
                return _unityVersion.Major;
            }
        }

        public int UnityVersionMinor
        {
            get
            {
                return _unityVersion.Minor;
            }
        }

        public int UnityVersionRev
        {
            get
            {
                return _unityVersion.Rev;
            }
        }

        public int UnityVersionBuild
        {
            get
            {
                return _unityVersion.Build;
            }
        }

        public bool UnityVersionIsCoRes
        {
            get
            {
                return _unityVersion.IsCoResident;
            }
        }

        //Details from the Connection database that are often needed for common function calls.
        public string PrimaryLocationDisplayName { get; private set; }
        public string PrimaryLocationServerAddress { get; private set; }
        public string PrimaryLocationObjectId { get; private set; }
        public string PrimaryLocationDomainName { get; private set; }

        //ODBC Connection information including the user name we logged into the ODBC proxy with, the password for that account
        //and server name attached to and it's IP address.
        public string DatabaseLastError { get; private set; }
        public string DatabaseLoginName { get; private set; }
        public string DatabaseLoginPassword { get; private set; }
        public string DatabaseServerName { get; private set; }
        public string DatabaseServerIpAddress { get; private set; }

        //the name of the database (UnityDirDB, UnityDynDb etc...) that we're connection to via ODBC.  By default this is 
        //UnityDirDb which is the main directory.
        public string CurrentDatabaseName { get; private set; }

        /// <summary>
        /// In Connection 9.5 (10.0) and later there is the concept of immediately attached neighbors in a network - the list of location ObjectIds for those
        /// servers is constructed at login and stored here.
        /// </summary>
        public List<string> NeighborLocations { get; set; }

        //so the current ADOdb connection status can be checked easily.
        public ConnectionStatus DatabaseConnectionStatus { get; private set; }

        public IConnectionDatabaseFunctions DatabaseFunctions { get; private set; }
        
        //iIndex is used to keep track of which ConnectionServerOdbc this in in the list - when events are raised for connection
        //status changes this makes it quicker to know which row in the grid is the visual representation of this Connection
        //server so we can update the status quickly in the event handler.
        public int ClassIndex { get; private set; }

        //keep track of how many instances of this class are around - not terribly critical for most tools but for items that have connections
        //to multiple Connection servers at once it can be used for cleanup sequences.
        private static int _classInstanceCount;

        /// <summary>
        /// List of language codes for installed TUI languages on Connection
        /// </summary>
        private List<int> _installedLanguages;

        /// <summary>
        /// default TUI language installed
        /// </summary>
        private int _defaultLanguage = -1;

        public override string ToString()
        {
            return string.Format("{0}, version:{1}", DatabaseServerName, UnityVersionString);
        }

        #endregion


        #region Constructor and Destructors

        /// <summary>
        /// Alternate constructor that does not take a form reference - yes, you can pass a null reference easily enough, however because
        /// it references a WinForm it requires you include the system.windows.forms library which can be annoying if you're making a 
        /// console application or the like.
        /// </summary>
        public ConnectionServerOdbc(IConnectionDatabaseFunctions pDatabaseFuntions)
        {
            if (pDatabaseFuntions == null)
            {
                throw new ArgumentException("Null instance of IConnectionDatabaseFunctions passed to ConnectionServerOdbc constructor");
            }

            DatabaseFunctions = pDatabaseFuntions;

            _classInstanceCount++;

            CurrentDatabaseName = "UnityDirDB";

            //this instance is assigned this "slot" which will correspond to what row in the grid of networked servers the user is
            //shown - this makes it easy to handle events raised by the various ConnectionServerOdbc login events and keep the grid updated.
            ClassIndex = _classInstanceCount;
            DatabaseConnectionStatus = ConnectionStatus.OffLine;
        }

        /// <summary>
        /// Constructor that allows the location objectId to be passed in - normally this is fetched when we log into the Connection server
        /// directly but in cases where ConnectionServerOdbc objects are being created from a network topology DB fetch we aren't yet logging in
        /// to each server and yet we need a unique identifier to find/fetch servers from the collection - that unique Id is the location 
        /// objectId so we need it in the constructor in that case.
        /// </summary>
        /// <param name="pLocationObjectId">
        /// Primary LocaitonObjectID for the Connection server.
        /// </param>
        /// <param name="pDatabaseFunctions"></param>
        public ConnectionServerOdbc(string pLocationObjectId,IConnectionDatabaseFunctions pDatabaseFunctions):this(pDatabaseFunctions)
        {
            PrimaryLocationObjectId = pLocationObjectId;
        }


        //when destroying the instance decrement the ClassInstanceCount so we have an accurate count of how many instances are still 
        //around if we should need such a thing.
        ~ConnectionServerOdbc()
        {
            _classInstanceCount--;
            Dispose(false);
        }

        //implement the disposable over rides 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // dispose managed resources
                try
                {
                    if (DatabaseFunctions != null)
                    {
                        DatabaseFunctions.Disconnect();
                    }
                }
                catch (Exception ex)
                {
                    RaiseErrorEvent("error disposing ConnectionServer class:"+ex);
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        #endregion


        #region Logging and Error Events

        /// <summary>
        /// Event handle for external clients to register with so they can get logging events on errors and warnings that happen
        /// within this class.
        /// </summary>
        public event LoggingEventHandler ErrorEvents;

        /// <summary>
        /// Debug events can be registered for and recieved to view raw send/response text
        /// </summary>
        public event LoggingEventHandler DebugEvents;

        /// <summary>
        /// Alternative event handler for logging events that includes the LogEventArgs that include the log string in the 
        /// argument
        /// </summary>
        public delegate void LoggingEventHandler(object sender, LogEventArgs e);

        /// <summary>
        /// If there's one or more clients registered for the ErrorEvent event then issue it here.
        /// </summary>
        /// <param name="pLine">
        /// String to pass back to the receiving method
        /// </param>
        internal void RaiseErrorEvent(string pLine)
        {
            DatabaseLastError = pLine;

            //notify registered clients 
            LoggingEventHandler handler = ErrorEvents;

            if (handler != null)
            {
                LogEventArgs oArgs = new LogEventArgs(pLine);
                handler(null, oArgs);
            }
        }

        /// <summary>
        /// If there's one or more clients registerd for the DebugEvents event then issue it here.
        /// </summary>
        /// <param name="pLine">
        /// String to pass back to the receiving method
        /// </param>
        private void RaiseDebugEvent(string pLine)
        {
            if (DebugMode == false) return;

            //notify registered clients
            LoggingEventHandler handler = DebugEvents;

            if (handler != null)
            {
                LogEventArgs oArgs = new LogEventArgs(pLine);
                handler(null, oArgs);
            }
        }

        #endregion


        #region Helper Methods
        
        /// <summary>
        /// get the primary locaiton information for the server we're connecting to.  This includes the primary location objectID,
        /// the display name of the location, the domain name it lives in, the server address and the version information.
        /// </summary>
        private void FillLocationProperties()
        {
            RaiseDebugEvent("[DEBUG] entering FillLocationProperties on ConnectionServerOdbc.cs");

            DataTable oTableLocations;

            //there should only ever be one row returned from this query - if not we have a problem
            DbFetchResult ret = DatabaseFunctions.FillDataTableBlocking("SELECT * FROM vw_LocalVMS", out oTableLocations);
            if (ret.Successful == false)
            {
                RaiseErrorEvent("(error) fetching location properties in FillLocationProperties on ConnectionServerOdbc.cs:" + ret.ErrorDetails);
            }

            if ((oTableLocations != null) && oTableLocations.Rows.Count==1)
            {
                PrimaryLocationDisplayName = oTableLocations.Rows[0]["DisplayName"].ToString();
                PrimaryLocationObjectId = oTableLocations.Rows[0]["ObjectID"].ToString();
                PrimaryLocationDomainName = oTableLocations.Rows[0]["SMTPDomain"].ToString();
                PrimaryLocationServerAddress = oTableLocations.Rows[0]["HostAddress"].ToString();
            }

            //get the neighbors list
            NeighborLocations = new List<string>();

            if (!IsConnectionVersionAtLeast(9, 5, 0, 0))
            {
                //this is a pre 9.5 installation, all network nodes are "meshed" together - everyone is a neighbor of everyone else
                ret =DatabaseFunctions.FillDataTableBlocking("Select ObjectId FROM vw_Location WHERE ObjectId <> ? ",
                    out oTableLocations, false, PrimaryLocationObjectId);
                
                if (!ret.Successful | oTableLocations == null)
                {
                    RaiseErrorEvent("Null table locations returned in FillLocationProperties:" + ret);
                    return;
                }

                foreach (DataRow oRow in oTableLocations.Rows)
                {
                    NeighborLocations.Add(oRow[0].ToString());
                }
            }
            else
            {
                //Post 9.5 we may be in an HTTP network where there is a hierarchy of nodes
                ret = DatabaseFunctions.FillDataTableBlocking(
                    "Select ObjectId FROM vw_NetworkTopology WHERE ObjectId <> NeighbourLocationObjectId " +
                    "AND NeighbourLocationObjectId = ?", out oTableLocations, false, PrimaryLocationObjectId);

                if (!ret.Successful | oTableLocations == null)
                {
                    RaiseErrorEvent("Null table locations returned in FillLocationProperties:"+ret);
                    return;
                }

                foreach (DataRow oRow in oTableLocations.Rows)
                {
                    NeighborLocations.Add(oRow[0].ToString());
                }
            }
        }

        

        /// <summary>
        /// if there's a login failure via ODBC process the error information we get back from the Informix ODBC driver.
        /// there's some common failure reasons that we can dig out and warn about in a friendly mannor - if it's something else
        /// just pass through what the ODBC driver raises.
        /// </summary>
        public string GetLoginErrorString(System.Data.Odbc.OdbcException ex)
        {
            if (ex == null)
            {
                return "";
            }

            string strErrorText = "The login attempt to " + DatabaseServerName + " failed." + "\n" + "\n";

            //check system errors returned by Informix - there's a couple of common ones users trip up on a lot.
            if (ex.Errors.Count > 0)
            {
                switch (ex.Errors[0].NativeError)
                {
                    case -908:
                        strErrorText += "System error -908 This usually means the server is not found or that the ODBC Proxy is not enabled or cannot be reached"
                                     + " on port 20532.  Make sure that port is not being blocked, that the database proxy service is currently active and running"
                                     + " and that the server name can be resolved properly from this Windows machine." + "\n";
                        break;
                    case -930:
                        strErrorText += "System error -930 This usually means the server is not found.  Make sure that the server name can be resolved properly from"
                                     + " this Windows machine." + "\n";
                        break;
                    case -11048:
                        strErrorText += "System error -11048 This usually means the ODBC driver is not installed properly or it can sometimes mean the PATH environment"
                                     + " variable is too long - try moving the INFORMIXDIR/bin reference to the beginning of the PATH." + "\n";
                        break;
                    case -27001:
                        strErrorText += "System error -27001 This usually means the user name or password are wrong or that they correspond to a user that does not have"
                                     + " the Remote Database Access role assigned to them or that the user\'s password has expired or is set to require a reset at next"
                                     + " login or the account has been locked." + "\n";
                        break;
                    default:
                        strErrorText += "Error retrned from ODBC Driver=" + ex.Message + "\n";
                        break;
                }
            }
            else
            {
                strErrorText += "Error retrned from ODBC Driver=" + ex.Message + "\n";
            }

            //store the error in the last error slot so the calling party can decide what to do with it - show it as a tool tip in the grid,
            //throw a message box up, passively show it in a status in the toolbar etc...
            strErrorText += "\n" + "Make sure Connection has been properly configured to allow for remote logins.  See the \'Troubleshooting Tips\' " +
                             "section in the help file for more details on this and other potential issues that could cause connection failures.";

            return strErrorText;

        }

        /// <summary>
        /// Disconnect from the database - this is used when failing to log into a server because the version is too old - allows for a clean 
        /// connection to another server with a newer version without having to exit and restart.
        /// </summary>
        public void LogoutBlocking()
        {
            RaiseDebugEvent("[DEBUG] entering LogoutBlocking on ConnectionServerOdbc.cs");
            DatabaseConnectionStatus = ConnectionStatus.OffLine;
            DatabaseFunctions.Disconnect();
        }

        /// <summary>
        /// Log into a remote Connection server using the ODBC proxy service interface.  This call is blocking which means the calling routine 
        /// will have to wait until it times out and returns and the user cannot cancel out of the login attempt cleanly.  Unfortunately the 
        /// ODBC driver's timeout doesn't adjust down properly so some login failures can be upwards of a minute before it returns which can be 
        /// annoying.  
        /// </summary>
        /// <param name="pLoginName" type="string">
        /// The login name of a user on the Connection server that has the remote database administration role assigned to them.
        /// </param>
        /// <param name="pPassword" type="string">
        /// The password of the login user.
        /// </param>
        /// <param name="pServerName" type="string">
        /// The name or IP address of the Connection server to log into.  The login name is converted to an IP address using the .NET DNS library 
        /// which is then used when logging in.
        /// </param>
        /// <param name="pProxyLoginName">
        /// If an HTTP proxy requires seperate authentication pass in the login name here, otherwise leave it blank
        /// </param>
        /// <param name="pProxyPassword">
        /// If an HTTP proxy requires seperate authentication pass in the password for it here, otherwise leave it blank
        /// </param>        
        /// <returns>
        /// TRUE is returned if the login completes, FALSE if there is an error.
        /// </returns>
        public DbFetchResult LoginDatabaseBlocking(string pLoginName, string pPassword, string pServerName, string pProxyLoginName = "", string pProxyPassword = "")
        {
            RaiseDebugEvent("[DEBUG] entering LoginDatabaseBlocking on ConnectionServerOdbc.cs");

            DatabaseLoginName = pLoginName;
            DatabaseLoginPassword = pPassword;
            DatabaseServerName = pServerName;

            DbFetchResult res = new DbFetchResult();
            res.SQLQuery = "Logging into server";

            try
            {
                DatabaseServerIpAddress = Dns.GetHostAddresses(pServerName)[0].ToString();
            }
            catch (Exception ex)
            {
                res.ErrorDetails=string.Format("Unable to resolve address via DNS: {0} - make sure the host name/IP address is correct and that your DNS " +
                           "configuration is functioning correclty:{1}",pServerName,ex.Message);
                
                RaiseErrorEvent(res.ErrorDetails);
                res.Successful = false;
            }

            res= DatabaseFunctions.AttachToDatabase(pServerName, pLoginName, pPassword);

            if (res.Successful == false)
            {
                return res;
            }

            //update the grid to show connected status
            DatabaseConnectionStatus = ConnectionStatus.Connected;

            //get version information for the server we've attached to
            InitVersionInfo();

            //fill in the properties for this location
            FillLocationProperties();

            return res;
        }

        /// <summary>
        /// Load version information of the Connection server currently attached.  This gets called immediately after a successful login to the ODBC server
        /// on Connection.
        /// </summary>
        private void InitVersionInfo()
        {
            RaiseDebugEvent("[DEBUG] entering InitVersionInfo on ConnectionServerOdbc.cs");

            DataTable dtTemp;

            //init everything to 0s
            _unityVersion.Major = 0;
            _unityVersion.Minor = 0;
            _unityVersion.Rev = 0;
            _unityVersion.Build = 0;
            _unityVersion.Es = "";
            _unityVersion.IsCoResident = false;
            _unityVersion.IsConnection = true;

            if (DatabaseFunctions.DatabaseConnectionStatus != ConnectionState.Open )
            {
                RaiseErrorEvent("(error) Login to remote Connection server is not currently open in InitVersionInfo - no version information can be retrieved.");
                return;
            }

            //first check if this is a co-res install
            string strTemp = DatabaseFunctions.GetConfigurationString("System.SystemSetup.PlatformConfiguration.deployment");
            if (strTemp.Length > 0)
            {
                if (strTemp.CompareTo("coresident") == 0)
                {
                    _unityVersion.IsCoResident = true;
                }
            }

            //now fetch the version string from the schema information
            DbFetchResult ret = DatabaseFunctions.FillDataTableBlocking("SELECT * FROM vw_Schemainformation WHERE IsCurrent=1", out dtTemp);
            if (ret.Successful == false)
            {
                RaiseErrorEvent("Unable to pull SchemaInformation data in InitVersionInfo - cannot fetch version.");
                return;
            }

            if (dtTemp.Rows.Count==0)
            {
                RaiseErrorEvent("Could not find current version information from remote database in InitVersionInfo");
                return;
            }

            //the product column will have the fully qualified version string in there - we need to pull it out and
            //parse it into its parts
            strTemp = dtTemp.Rows[0]["product"].ToString();

            //it must be at least 6 digits long (3 periods, 3 characters
            if (strTemp.Length < 6)
            {
                RaiseErrorEvent("Invalid version string pulled from database in InitVersionInfo on clsConnectionServer:" + strTemp);
                return;
            }

            string[] strVersion = strTemp.Split('.'.ToString().ToCharArray());

            //it must contain at least 3 parts seperated by periods
            if ((strVersion.Length - 1) != 3)
            {
                RaiseErrorEvent("Invalid version string pulled from database in InitVersionInfo on clsConnectionServer:" + strTemp);
                return;
            }

            //drop the version strings into their assigned slots
            if (Int32.TryParse(strVersion[0], out _unityVersion.Major) == false)
            {
                RaiseErrorEvent("(error) parsing Major version string in InitVersionInfo on ConnectionServerOdbc - defaulting to 0");
                _unityVersion.Major = 0;
            }

            if (Int32.TryParse(strVersion[1], out _unityVersion.Minor) == false)
            {
                RaiseErrorEvent("(error) parsing Minor version string in InitVersionInfo on ConnectionServerOdbc - defaulting to 0");
                _unityVersion.Minor = 0;
            }

            if (Int32.TryParse(strVersion[2], out _unityVersion.Rev) == false)
            {
                RaiseErrorEvent("(error) parsing Rev version string in InitVersionInfo on ConnectionServerOdbc - defaulting to 0");
                _unityVersion.Rev = 0;
            }


            //the 4th chunk may contain an "ES" string in here as well - it'll be a build number followed by ES and another number most
            //often but the teams can get a little wild with the format here
            if (strVersion[3].Contains("ES"))
            {
                //trim off the "ES" portion and drop the rest into the ES string for display purposes
                int iTemp = strVersion[3].IndexOf("ES", 0);

                string strEs = strVersion[3].Substring(iTemp + 2);
                _unityVersion.Es = strEs;

                //the ES will not always be a number but if it is, store it in the EsInt field for comparison purposes
                if (int.TryParse(strEs, out _unityVersion.EsInt) == false)
                {
                    //either no ES provided (base install) or the ES is not provided as a number
                    _unityVersion.EsInt = 0;
                }

                //the build will be on the begining (left) of the string before the ES if it's there
                strTemp = strVersion[3].Substring(0, iTemp - 1);
                if (int.TryParse(strTemp, out _unityVersion.Build) == false)
                {
                    //common enough garbage in this field, don't log an error, just set it to 0
                    _unityVersion.Build = 0;
                }
            }
            else
            {
                //no es string included - that's ok, it's just the build number then, or it's blank (0)
                if (int.TryParse(strVersion[3], out _unityVersion.Build) == false)
                {
                    //common enough garbage in this field, don't log an error, just set it to 0
                    _unityVersion.Build = 0;
                }
            }
        }

        /// <summary>
        /// returns true if the version of the Connection server is greater than or equal to the version passed in.
        /// The version string for Connection breaks down like this:
        /// Major.Minor(Build.Rev)
        /// </summary>
        /// <param name="pMajor" type="int">
        ///         The major version of Connection to compare against.
        /// </param>
        /// <param name="pMinor" type="int">
        ///         The Minor version of Connection number to compare against.
        /// </param>
        /// <param name="pRev" type="int">
        ///         The Revision version of Connection to compare against.
        /// </param>
        /// <param name="pBuild" type="int">
        ///         The Build number of Connection to compare against.
        /// </param>
        /// <param name="pEs">
        /// If the ES is provided as an integer in the version string it's provided here - for some installs this will not work as in older 7.x
        /// builds the ES was often a string and not a number - in that case the Es comparision will be against 0.
        /// </param>
        /// <returns>
        ///     True returns if the currently attached Connection server is the same version or later than the version passed in.
        /// </returns>
        public bool IsConnectionVersionAtLeast(int pMajor, int pMinor, int pRev, int pBuild, int pEs = 0)
        {
            return _unityVersion.IsVersionAtLeast(pMajor, pMinor, pRev, pBuild, pEs);
        }


        /// <summary>
        ///     Changes which active Connection database the instance of this ConnectionServerOdbc is currently attached to.  By default we're alwasy attached to 
        ///     UnityDirDB and in most cases that's fine - fetching data from other datbases can be done via SQL syntax (i.e. "UnityRptDB:vw_...").  Some tools, such 
        ///     as CUDLI for instance, have a need to attach to a specific database so the function is provided here.
        /// </summary>
        /// <param name="pNewDbName" type="string">
        ///         The database name to attach to (UnityDirDB, UnityRptDB, UnityDynDB etc...).  
        /// </param>
        /// <returns>
        ///     TRUE is returned if it attaches OK, FALSE otherwise.
        /// </returns>
        public bool ChangeActiveDatabase(string pNewDbName)
        {
            RaiseDebugEvent("[DEBUG] entering ChangeActiveDatabase on ConnectionServerOdbc.cs");

            //validate the name
            if (string.IsNullOrEmpty(pNewDbName))
            {
                RaiseErrorEvent("(error) Empty database name passed to ChangeActiveDatabase in clsConnectionServer");
                DatabaseConnectionStatus = ConnectionStatus.LoginFailed;

                return false;
            }

            //make sure the DB name is valid
            switch (pNewDbName.ToUpper())
            {
                case "UNITYDIRDB":
                case "UNITYDYNDB":
                case "UNITYRPTDB":
                    break;
                case "UNITYMBXDB1":
                case "UNITYMBXDB2":
                case "UNITYMBXDB3":
                case "UNITYMBXDB4":
                case "UNITYMBXDB5":
                case "UNITYMBXDB6":
                    //check to see if the mailbox exists?  Shouldn't be necessary since the mailstore drop down menu pulls from the DB directly
                    break;
                default:
                    DatabaseConnectionStatus = ConnectionStatus.LoginFailed;
                    RaiseErrorEvent("(error) Invalid database name passed to ChangeActiveDatabase in clsConnectionServer:" + pNewDbName);
                    return false;
            }

            CurrentDatabaseName = pNewDbName;

            DatabaseFunctions.Disconnect();

            var res=DatabaseFunctions.AttachToDatabase(this.DatabaseServerIpAddress, this.DatabaseLoginName,this.DatabaseLoginPassword);

            if (res.Successful == false)
            {
                DatabaseConnectionStatus = ConnectionStatus.LoginFailed;
                RaiseErrorEvent("(error) while changing databases in ChangeActiveDatabase: " + res);
            }

            //update the status to show connected status
            DatabaseConnectionStatus = ConnectionStatus.Connected;

            return true;
        }

        

        /// <summary>
        ///this function calls the stored procs necessary to tell the grammar compilation engine to wait until we're done before recompiling
        ///anything - since we're potentially adding/changing many items this prevents thrash.
        ///This also calls the network replication pause for the same reason - I'm sure there may be more things folks wish to pause so I'm
        ///moving this out into a seperate routine here.
        ///Both these calls pause their respective capabilities for 10 minutes - so this needs to be called again based on how long the import
        ///operation is taking.
        /// </summary>
        /// <param name="pPauseNetworking">
        /// When restoring hot mode systems you do not want to pause networking functions, otherwise you want to stop them in addition to 
        /// the grammar compiles when doing large numbers of edits.
        /// </param>
        public void LargeDbOperationPause(bool pPauseNetworking)
        {
            RaiseDebugEvent("[DEBUG] entering LargeDbOperationPause on ConnectionServerOdbc.cs");

            if (DatabaseFunctions.StartNewCommand("csp_grammarcompilepause") == false)
            {
                return;
            }

            DbFetchResult res = DatabaseFunctions.ExecuteProc();
            if (res.Successful == false)
            {
                RaiseErrorEvent("(error) in LargeDbOperationPause pausing grammar compiles on ConnectionServerOdbc.cs:"+res);
            }

            
            if (pPauseNetworking)
            {
                if (DatabaseFunctions.StartNewCommand("csp_networkreplicationpause") == false)
                {
                    return;
                }
                res = DatabaseFunctions.ExecuteProc();
                if (res.Successful == false)
                {
                    RaiseErrorEvent("(error) in LargeDbOperationPause pausing networking replication on ConnectionServerOdbc.cs:" +
                        res);
                }
            }

            //'make a note off the time we called these pauses - we'll need to call this again if the import takes more than 10 minutes.
            TimeOfLastLargeOperationsPause = DateTime.Now;
        }


        /// <summary>
        /// When we create users in a mailstore we need to force it to reindex (update statistics) or message imports into that mailstore
        /// will grind to a halt.  This is technically true only if the mailstore is completely empty and then you add a bunch of users and then
        /// a bunch of messages - but to be safe we force a reindex after adding users to any mailstore here - it's pretty quick and does no harm at
        /// any rate.
        /// </summary>
        /// <param name="pMailstoreName">
        /// The mailstore name to update - most systems have only one mailstore but it's possible there are up to 5.
        /// </param>
        public DbFetchResult UpdateStatisticsForMailstore(string pMailstoreName)
        {
            RaiseDebugEvent("[DEBUG] entering UpdateStatisticsForDatabase on ConnectionServerOdbc.cs");
            return DatabaseFunctions.UpdateStatisticsForDatabase(pMailstoreName, DatabaseServerName,DatabaseLoginName, DatabaseLoginPassword);
        }

        #endregion


        #region Language Related Methods

        /// <summary>
        /// Helper function that takes a language code (i.e. 1033 for ENU) as a string instead of an Int (as it comes out 
        /// of a data reader or table interface) and returns if that language is installed or not.
        /// </summary>
        /// <param name="pLanguageCode"></param>
        /// <returns>
        /// True if the language is installed, false if not.
        /// </returns>
        public bool IsLanguageInstalled(string pLanguageCode)
        {
            RaiseDebugEvent("[DEBUG] entering IsLanguageInstalled with string language code on ConnectionServerOdbc.cs");

            int iTemp;
            if (int.TryParse(pLanguageCode, out iTemp))
            {
                return IsLanguageInstalled(iTemp);
            }
            return false;
        }

        /// <summary>
        /// Returns true or false if the passed in language code (i.e. 1033 for ENU) is installed on the Connection server.
        /// </summary>
        /// <param name="pLanguageCode"></param>
        /// <returns>
        /// True if the language is installed, false if not.
        /// </returns>
        public bool IsLanguageInstalled(int pLanguageCode)
        {
            RaiseDebugEvent("[DEBUG] entering IsLanguageInstalled with integer language code on ConnectionServerOdbc.cs");
            
            //do a "lazy fetch" of the languages installed
            if (_installedLanguages == null)
            {
                GetInstalledLanguages();
            }

            if (_installedLanguages == null || _installedLanguages.Count == 0)
            {
                return false;
            }

            return _installedLanguages.Contains(pLanguageCode);
        }

        /// <summary>
        /// Get the default TUI language installed on the system.
        /// </summary>
        /// <returns>
        /// Integer for the language code (i.e. 1033 for ENU)
        /// </returns>
        public int GetDefaultLanguage()
        {
            RaiseDebugEvent("[DEBUG] entering GetDefaultLanguage on ConnectionServerOdbc.cs");

            if (_defaultLanguage!=-1)
            {
                return _defaultLanguage;
            }

            string strLang = DatabaseFunctions.GetConfigurationString("System.SystemSetup.TUILanguages.Default");
            _defaultLanguage = LanguageHelper.GetLanguageIdFromShortName(strLang);
            return _defaultLanguage;
        }

        /// <summary>
        /// Fetches the list of installed TUI languages and stores them in the private _installedLanguages generic list of 
        /// integers.  We use "lazy fetch" here so this is only called when someone references a language fetch method.
        /// </summary>
        private void GetInstalledLanguages()
        {
            RaiseDebugEvent("[DEBUG] entering GetInstalledLanguages on ConnectionServerOdbc.cs");

            _installedLanguages = new List<int>();

            string strLanguages = DatabaseFunctions.GetConfigurationString("System.SystemSetup.TUILanguages.Installed");

            if (string.IsNullOrEmpty(strLanguages))
            {
                return;
            }

            //the languages are stored in short codes, convert them to ints and add them to the list of installed languages
            foreach (string strCode in strLanguages.Split(','))
            {
                ShortLanguageNames oLanguageCode;
                if (Enum.TryParse(strCode, true, out oLanguageCode))
                {
                    int iLang = (int) oLanguageCode;
                    _installedLanguages.Add(iLang);
                }
            }
        }

        #endregion


        #region Static Helper Methods

        /// <summary>
        /// This function checks for a number value that's positive and has
        /// no decimals.  This also takes a range value in if you would like to check that the extension falls within that range.
        /// In some cases extensions are optional (for instance on call handlers) so the bAllowBlank flag is used to include that
        /// check, however it's off by default.
        /// </summary>
        /// <param name="pExtension" type="string">
        /// The extension string to validate.
        /// </param>
        /// <param name="pMinimumDigits" type="int">
        /// Optional value for minimum length - defaults to 0 which is legal on many objects (though not users).
        /// </param>
        /// <returns>
        /// TRUE is returned if the string is a valid extension, FALSE otherwise.
        /// </returns>
        public static bool IsValidExtension(string pExtension, int pMinimumDigits = 0)
        {
            if (string.IsNullOrEmpty(pExtension))
            {
                if (pMinimumDigits==0)
                {
                    //special case of blank being valid - for handlers this is true, for users it is not
                    return true;
                }
                return false;
            }

            pExtension = pExtension.Trim();

            if (pExtension.Length < pMinimumDigits)
            {
                return false;
            }

            long lTemp;

            if (long.TryParse(pExtension, out lTemp) == false)
            {
                return false;
            }

            //check for negative
            if (lTemp < 0)
            {
                return false;
            }

            //if you get this far, everything is cool
            return true;

        }

        /// <summary>
        /// booleans are handled differently in Access than in SQL - force a true value to return 1 and a false value to return 0 here
        /// just to be extra safe
        /// </summary>
        /// <param name="pBool">
        /// boolean value to convert
        /// </param>
        /// <returns>
        /// 0 or 1 depending on boolean value passed in.
        /// </returns>
        public static int IntFromBool(bool pBool)
        {
            if (pBool)
            {
                return 1;
            }
            return 0;
        }

        /// <summary>
        /// Simple method that determins if the database name string is a valid Unity Connection database
        /// </summary>
        /// <param name="pDbName"></param>
        /// <returns></returns>
        public static bool IsValidDatabaseName(string pDbName)
        {
            //make sure the DB name is valid
            switch (pDbName.ToUpper())
            {
                case "UNITYDIRDB":
                case "UNITYDYNDB":
                case "UNITYRPTDB":
                case "UNITYMBXDB1":
                case "UNITYMBXDB2":
                case "UNITYMBXDB3":
                case "UNITYMBXDB4":
                case "UNITYMBXDB5":
                case "UNITYMBXDB6":
                    return true;
            }

            return false;
        }

        #endregion

    }



}

Commits for ConnectionOdbcSdk/trunk/ConnectionServer/ConnectionServerOdbc.cs

Diff revisions: vs.
Revision Author Commited Message
9 Diff Diff jlindborg picture jlindborg Fri 30 Aug, 2013 19:10:27 +0000

more unit and integration test setup work.

8 Diff Diff jlindborg picture jlindborg Fri 30 Aug, 2013 00:27:58 +0000

adding some unit/integration tests into the project, doing a little cleanup.

4 jlindborg picture jlindborg Sat 01 Jun, 2013 02:52:17 +0000

reworking project structure and naming