-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathQuickBaseDesktop.cls
More file actions
1665 lines (1471 loc) · 71.8 KB
/
QuickBaseDesktop.cls
File metadata and controls
1665 lines (1471 loc) · 71.8 KB
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
989
990
991
992
993
994
995
996
997
998
999
1000
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "QBDesktop"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Compare Binary
Option Explicit
Option Base 0
'© 2001 Intuit Inc. All rights reserved.
'Use is subject to the IP Rights Notice and Restrictions available at
'http://developer.intuit.com/legal/IPRNotice_021201.html
Public QDBHost As String
Public useHTTPS As Boolean
Public qdb As QuickBaseClient
Public qdbResponse As New MSXML.DOMDocument
Const UPDATE_ID_FIELDNAME = "_qdb_update_id_"
Public MinutesGMTOffset As Integer
Sub displayEditingForm(strDBID As String, mode As Integer)
DoCmd.OpenForm "frm_qdb_" + strDBID, mode
End Sub
Function downloadAttachedFile(DBID As String, strFileName As String)
Dim fs As Variant
Dim f As Variant
Set fs = CreateObject("Scripting.FileSystemObject")
If fs.FileExists(Application.CurrentProject.Path + "\" + DBID + "\" + strFileName) Then
fs.deleteFile Application.CurrentProject.Path + "\" + DBID + "\" + strFileName, True
End If
Call qdb.downloadAttachedFile(DBID, Application.CurrentProject.Path + "\" + DBID, strFileName)
Set f = fs.GetFile(Application.CurrentProject.Path + "\" + DBID + "\" + strFileName)
If Not (f.Attributes = f.Attributes And 1) Then
f.Attributes = f.Attributes + 1
End If
End Function
Sub updateViewsTable(strDBID As String)
Dim rst As ADODB.Recordset
Dim i As Integer
Dim j As Integer
Dim strFID As String
Dim strCriteria As String
Dim strFIDs() As String
Dim strQuery As String
Dim strOptions As String
Dim strCriteriaParts() As String
Dim strFieldName As String
Dim strViewName As String
Dim strQID As String
Dim strSQL As String
Dim ViewNodeList As MSXML.IXMLDOMNodeList
Dim FieldNodeList As MSXML.IXMLDOMNodeList
Dim strWHERE As String
Dim strORDERBY As String
Set ViewNodeList = qdbResponse.documentElement.selectNodes("/*/table/queries/query")
'get the List All field list from the field node list
Set FieldNodeList = qdbResponse.documentElement.selectNodes("/*/table/fields/field[appears_by_default=1]")
'Clean out the view descriptions in the qdbViews table
DoCmd.SetWarnings (False)
DoCmd.RunSQL ("DELETE FROM qdbViews WHERE (((qdbViews.DBID)='" + strDBID + "'));")
DoCmd.SetWarnings (True)
Set rst = New ADODB.Recordset
rst.Open "qdbViews", CurrentProject.Connection, adOpenForwardOnly, adLockOptimistic
For i = 0 To ViewNodeList.length - 1
If ViewNodeList(i).selectNodes("qyopts").length > 0 Then
strOptions = ViewNodeList(i).selectSingleNode("qyopts").nodeTypedValue
If InStr(strOptions, "xst.") = 0 And InStr(strOptions, "xfl.") = 0 Then
rst.AddNew
rst("DBID") = strDBID
rst("Name") = makeMSAccessObjectName(ViewNodeList(i).selectSingleNode("qyname").nodeTypedValue)
rst("QID") = CInt(ViewNodeList(i).selectSingleNode("@id").nodeTypedValue)
strSQL = "SELECT "
If ViewNodeList(i).selectNodes("qyclst").length > 0 Then
strFIDs = Split(ViewNodeList(i).selectSingleNode("qyclst").nodeTypedValue, ".")
Else
'get the List All clist from the field node list
ReDim strFIDs(FieldNodeList.length - 1)
For j = 0 To FieldNodeList.length - 1
strFIDs(j) = FieldNodeList(j).selectSingleNode("@id").Text
Next j
End If
For j = 0 To UBound(strFIDs)
If strFIDs(j) <> "-1" Then
strFieldName = makeMSAccessObjectName(qdbResponse.documentElement.selectSingleNode("/*/table/fields/field[@id=" + strFIDs(j) + "]/label").Text)
strSQL = strSQL + "qdb_" + strDBID + ".[" + strFIDs(j) + "] AS [" + strFieldName + "], "
End If
Next j
strSQL = Trim(strSQL)
If Right(strSQL, 1) = "," Then
strSQL = Left(strSQL, Len(strSQL) - 1)
End If
strSQL = strSQL + " FROM qdb_" + strDBID + " "
'parse the query for the where clause
strQuery = ViewNodeList(i).selectSingleNode("qycrit").nodeTypedValue
If strQuery <> "{'0'.CT.''}" Then
strSQL = strSQL + " WHERE "
strWHERE = ""
Do While InStr(strQuery, "{") > 0
strCriteria = Mid(strQuery, InStr(strQuery, "{") + 1, InStr(strQuery, "}") - InStr(strQuery, "{") - 1)
strQuery = Mid(strQuery, InStr(strQuery, "}") + 1)
strCriteriaParts = Split(strCriteria, ".")
strFID = Mid(strCriteriaParts(0), 2, Len(strCriteriaParts(0)) - 2)
strWHERE = strWHERE + "qdb_" + strDBID + ".[" + strFID + "] "
'If we have a checkbox field we need to set the criteria to true or false
strCriteriaParts(2) = Mid(strCriteriaParts(2), 2, Len(strCriteriaParts(2)) - 2)
If qdbResponse.documentElement.selectSingleNode("/*/table/fields/field[@id=" + strFID + "]/@base_type").Text = "bool" Then
If UCase(strCriteriaParts(2)) = "YES" Or UCase(strCriteriaParts(2)) = 1 Or UCase(strCriteriaParts(2)) = "true" Then
strCriteriaParts(2) = "_True_"
Else
strCriteriaParts(2) = "_False_"
End If
End If
Select Case strCriteriaParts(1)
Case "CT" 'Contains
strWHERE = strWHERE + "like '*" + strCriteriaParts(2) + "*' AND "
Case "XCT" 'Does Not contain
strWHERE = strWHERE + "not like '*" + strCriteriaParts(2) + "*' AND "
Case "EX" 'Is
strWHERE = strWHERE + "= '" + strCriteriaParts(2) + "' AND "
Case "XEX" 'Is not
strWHERE = strWHERE + "!= '" + strCriteriaParts(2) + "' AND "
Case "SW" 'Starts with
strWHERE = strWHERE + "like '" + strCriteriaParts(2) + "*' AND "
Case "XSW" 'Does not start with
strWHERE = strWHERE + "not like '" + strCriteriaParts(2) + "*' AND "
Case "BF" 'Is before
strWHERE = strWHERE + "< #" + strCriteriaParts(2) + "# AND "
Case "OBF" 'Is on or before
strWHERE = strWHERE + "<= #" + strCriteriaParts(2) + "# AND "
Case "AF" 'Is after
strWHERE = strWHERE + "> #" + strCriteriaParts(2) + "# AND "
Case "OAF" 'Is on or after
strWHERE = strWHERE + ">= #" + strCriteriaParts(2) + "# AND "
Case "LT" 'Is less than
strWHERE = strWHERE + "< " + strCriteriaParts(2) + " AND "
Case "LTE" 'Is less than or equal to
strWHERE = strWHERE + "<= " + strCriteriaParts(2) + " AND "
Case "GT" 'Is greater than
strWHERE = strWHERE + "> " + strCriteriaParts(2) + " AND "
Case "GTE" 'Is greater than or equal to
strWHERE = strWHERE + ">= " + strCriteriaParts(2) + " AND "
End Select
Loop
strWHERE = Replace(strWHERE, "'_True_'", "True")
strWHERE = Replace(strWHERE, "'_False_'", "False")
'remove the trailing AND
strWHERE = Left(strWHERE, Len(strWHERE) - 4)
strSQL = strSQL + strWHERE
rst("WHERE") = strWHERE
End If
If ViewNodeList(i).selectNodes("qyslst").length > 0 Then
strSQL = strSQL + "ORDER BY "
strORDERBY = ""
'parse the slist in a loop
strFIDs = Split(ViewNodeList(i).selectSingleNode("qyslst").nodeTypedValue, ".")
For j = 0 To UBound(strFIDs)
strORDERBY = strORDERBY + "qdb_" + strDBID + ".[" + strFIDs(j) + "] "
If Mid(strOptions, j + InStr(strOptions, "sortorder-") + 10, 1) = "A" Then
strORDERBY = strORDERBY + "ASC, "
Else
strORDERBY = strORDERBY + "DESC, "
End If
Next j
strORDERBY = Trim(strORDERBY)
If Right(strORDERBY, 1) = "," Then
strORDERBY = Left(strORDERBY, Len(strORDERBY) - 1)
End If
End If
rst("ORDERBY") = strORDERBY
rst("SQL") = Trim(strSQL + strORDERBY) + ";"
rst.Update
End If
End If
Next i
End Sub
Function QueryValueEx(ByVal lhKey As Long, ByVal szValueName As _
String, vValue As Variant) As Long
Dim cch As Long
Dim lrc As Long
Dim lType As Long
Dim lValue As Long
Dim sValue As String
On Error GoTo QueryValueExError
' Determine the size and type of data to be read
lrc = RegQueryValueExNULL(lhKey, szValueName, 0&, lType, 0&, cch)
If lrc <> ERROR_NONE Then Error 5
Select Case lType
' For strings
Case REG_SZ:
sValue = String(cch, 0)
lrc = RegQueryValueExString(lhKey, szValueName, 0&, lType, _
sValue, cch)
If lrc = ERROR_NONE Then
vValue = Left$(sValue, cch - 1)
Else
vValue = Empty
End If
' For DWORDS
Case REG_DWORD:
lrc = RegQueryValueExLong(lhKey, szValueName, 0&, lType, _
lValue, cch)
If lrc = ERROR_NONE Then vValue = lValue
Case Else
'all other data types not supported
lrc = -1
End Select
QueryValueExExit:
QueryValueEx = lrc
Exit Function
QueryValueExError:
Resume QueryValueExExit
Exit Function
End Function
Function QueryValue(sKeyName As String, sValueName As String) As Variant
Dim lRetVal As Long 'result of the API functions
Dim hKey As Long 'handle of opened key
Dim vValue As Variant 'setting of queried value
lRetVal = RegOpenKeyEx(HKEY_LOCAL_MACHINE, sKeyName, 0, _
KEY_QUERY_VALUE, hKey)
lRetVal = QueryValueEx(hKey, sValueName, vValue)
QueryValue = vValue
RegCloseKey (hKey)
End Function
Function getCompleteCList(DBID As String) As String
Dim clist As String
Dim fieldcount As Integer
Dim strSQL As String
Dim rstFields As ADODB.Recordset
clist = ""
strSQL = "SELECT qdbFields.*, qdbFields.DBID FROM qdbFields WHERE ((qdbFields.DBID)='" + DBID + "');"
fieldcount = 0
Set rstFields = New ADODB.Recordset
rstFields.Open strSQL, CurrentProject.Connection, adOpenForwardOnly
Do While Not rstFields.EOF
clist = clist + rstFields("FID").Value + "."
fieldcount = fieldcount + 1
rstFields.MoveNext
Loop
rstFields.Close
getCompleteCList = clist
End Function
Function getDateModifiedFID(DBID As String) As String
Dim strSQL As String
Dim rstRecords As ADODB.Recordset
strSQL = "SELECT qdbFields.FID FROM qdbFields WHERE (((qdbFields.DBID)='" + DBID + "') AND ((qdbFields.Role)='modified'));"
Set rstRecords = New ADODB.Recordset
rstRecords.Open strSQL, CurrentProject.Connection, adOpenForwardOnly
rstRecords.MoveFirst
getDateModifiedFID = rstRecords("FID")
rstRecords.Close
End Function
Function getServerChanges(DBID As String, qdb As QuickBaseClient, localDateModifiedBeforeSync As Date, DateModifiedOfLastEditedRecord As Date, maxRidBeforeSync As Integer, ridOfFirstNewRecord As Integer, strDateModifiedLabel As String, strRecordIDFID As String) As String
Dim clist As String
Dim fieldcounter As Integer
Dim recordcounter As Integer
Dim newRecordCounter As Integer
Dim editedRecordCounter As Integer
Dim RecordNodeList As MSXML.IXMLDOMNodeList
Dim FieldNodeList As MSXML.IXMLDOMNodeList
Dim CellNodeList As MSXML.IXMLDOMNodeList
Dim strFIDs() As String
Dim rst As ADODB.Recordset
Dim query As String
Dim strStart As String
Dim dateModified As Date
Dim currentRID As String
newRecordCounter = 0
editedRecordCounter = 0
clist = getCompleteCList(DBID)
Set rst = New ADODB.Recordset
rst.Open "qdb_" + DBID, CurrentProject.Connection, adOpenKeyset, adLockOptimistic, adCmdTableDirect
rst.Index = strRecordIDFID
strStart = Format(localDateModifiedBeforeSync, "mm/dd/yyyy")
'Get all the records that changed since the last sync
'They're sorted from oldest to newest
query = "{'" + strDateModifiedLabel + "'.OAF.'" + strStart + "'}"
Set qdbResponse = qdb.DoQuery(DBID, query, clist, strDateModifiedLabel, "sortorder-A")
Set FieldNodeList = qdbResponse.documentElement.selectNodes("/*/table/fields/field")
ReDim strFIDs(FieldNodeList.length)
For fieldcounter = 0 To FieldNodeList.length - 1
strFIDs(fieldcounter) = FieldNodeList(fieldcounter).selectSingleNode("@id").Text
Next fieldcounter
Set RecordNodeList = qdbResponse.documentElement.selectNodes("/*/table/records/record")
For recordcounter = 0 To RecordNodeList.length - 1
Set CellNodeList = RecordNodeList(recordcounter).selectNodes("f")
dateModified = qdb.int64ToDate(RecordNodeList(recordcounter).selectSingleNode("f[@id='" + strDateModifiedLabel + "']").Text)
currentRID = RecordNodeList(recordcounter).selectSingleNode("f[@id='" + strRecordIDFID + "']").Text
If dateModified <= DateModifiedOfLastEditedRecord And _
(CInt(currentRID) < ridOfFirstNewRecord Or ridOfFirstNewRecord = 0) Then
'We need to make this edit
rst.Seek CInt(currentRID)
If rst.EOF Then 'This is a new record so we need to add it
rst.AddNew
newRecordCounter = newRecordCounter + 1
End If
updateLocalRecord DBID, rst, CellNodeList, strFIDs(), FieldNodeList, RecordNodeList(recordcounter).selectSingleNode("f[@id='" + strDateModifiedLabel + "']").Text
rst.Update
End If
Next recordcounter
rst.Close
getServerChanges = "Local DB: " + CStr(editedRecordCounter) + " records edited, " + CStr(newRecordCounter) + " records added."
End Function
Function HideConflictScreen(DBID As String)
Access.Forms("conflict_qdb_" + DBID).Visible = False
End Function
Function CloseConflictScreen(DBID As String)
DoCmd.Close acForm, "conflict_qdb_" + DBID
End Function
Sub DisplayError()
Select Case Err.Number - vbObjectError
Case 3
MsgBox "We're sorry that we could not access that QuickBase." + vbCrLf + _
"You do not have sufficent permissions to perform that operation." + vbCrLf + _
"Please try again after requesting additional permission from the" + vbCrLf + _
"owner or using a different e-mail address or screen name and password.", vbOKOnly, "QuickBase Desktop Insufficent Permission"
Case 4
MsgBox "We're sorry that we could not log you into QuickBase with the e-mail address or screen name you provided." + vbCrLf + _
"Or it could be that you've mistyped your password." + vbCrLf + _
"Please try again with a different e-mail address, screen name or password.", vbOKOnly, "QuickBase Desktop Login Problem"
Case 32
MsgBox "We're sorry that we could not access that QuickBase database." + vbCrLf + _
"It could be that someone has deleted this database." + vbCrLf + _
"Please try again with a different database name or click on the 'Refresh' button and try again.", vbOKOnly, "QuickBase Database not Found"
Case Else
MsgBox "QuickBase returned the following error:" + vbCrLf + Err.Description + vbCrLf + "Error Number: " + CStr(Err.Number - vbObjectError), vbOKOnly, "QuickBase Desktop Error " + CStr(Err.Number - vbObjectError)
End Select
End Sub
Function DeleteLocalDB(strDBID As String) As Boolean
Dim cat As ADOX.Catalog
Dim QDBTable As ADOX.Table
Dim QDBView As ADOX.View
Dim rstRecords As ADODB.Recordset
Dim strSQL As String
Dim strQueryName As String
Set cat = New ADOX.Catalog
cat.ActiveConnection = CurrentProject.Connection
strSQL = "SELECT qdbTables.Label FROM qdbTables WHERE (((qdbTables.DBID)='" + strDBID + "'));"
Set rstRecords = New ADODB.Recordset
rstRecords.Open strSQL, CurrentProject.Connection, adOpenForwardOnly
strQueryName = rstRecords("Label")
rstRecords.Close
On Error Resume Next
DoCmd.Close acForm, "frm_qdb_" + strDBID
DoCmd.DeleteObject acForm, "frm_qdb_" + strDBID
DoCmd.Close acForm, "conflict_qdb_" + strDBID
DoCmd.DeleteObject acForm, "conflict_qdb_" + strDBID
cat.Tables.Delete "qdb_" + strDBID
cat.Tables.Delete "orig_" + strDBID
cat.Tables.Delete strQueryName
On Error GoTo 0
'Need to walk through the forms collection to make sure the form is gone
'Need to walk the tables collection also
'Clean out the field descriptions in the qdbFields table
DoCmd.SetWarnings (False)
DoCmd.RunSQL ("DELETE FROM qdbFields WHERE (((qdbFields.DBID)='" + strDBID + "'));")
DoCmd.SetWarnings (True)
'Clean out the view descriptions in the qdbViews table
DoCmd.SetWarnings (False)
DoCmd.RunSQL ("DELETE FROM qdbViews WHERE (((qdbViews.DBID)='" + strDBID + "'));")
DoCmd.SetWarnings (True)
'Delete the querys
For Each QDBView In cat.Views
If strQueryName = QDBView.Name Then
cat.Views.Delete strQueryName
End If
Next QDBView
DeleteLocalDB = True
End Function
Sub GetGrantedDBs(strUsername As String, strPassword As String)
Dim DBNodeList As MSXML.IXMLDOMNodeList
Dim xmlNode As MSXML.IXMLDOMNode
Dim rst As ADODB.Recordset
Dim i As Integer
Dim strDBID As String
Dim cat As ADOX.Catalog
Dim QDBTable As ADOX.Table
Set cat = New ADOX.Catalog
cat.ActiveConnection = CurrentProject.Connection
i = qdb.Authenticate(strUsername, strPassword)
Set qdbResponse = qdb.GetGrantedDBs()
Set DBNodeList = qdbResponse.documentElement.selectNodes("/*/databases/dbinfo")
DoCmd.SetWarnings (False)
DoCmd.RunSQL ("DELETE FROM qdbTables WHERE qdbTables.isLocal=False;")
DoCmd.SetWarnings (True)
Set rst = New ADODB.Recordset
rst.Open "qdbTables", CurrentProject.Connection, adOpenForwardOnly, adLockOptimistic
For Each xmlNode In DBNodeList
rst.AddNew
rst("Label") = xmlNode.selectSingleNode("dbname").nodeTypedValue
rst("DBID") = xmlNode.selectSingleNode("dbid").nodeTypedValue
For Each QDBTable In cat.Tables
If "qdb_" + rst("DBID") = QDBTable.Name Then
rst("isLocal") = True
Call setTableDescription(QDBTable.Name, rst("Label"))
End If
Next QDBTable
On Error Resume Next
Err.Clear
rst.Update
If Err.Number <> 0 Then
rst.CancelUpdate
End If
Next xmlNode
Exit Sub
End Sub
Sub makeQueryForTable(DBID As String, DBName As String, strSQL As String)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim prpDescription As DAO.Property
Dim QueryTitle As String
QueryTitle = makeMSAccessObjectName(DBName)
Set db = CurrentDb()
On Error Resume Next
db.QueryDefs.Delete QueryTitle
On Error GoTo 0
Set qdf = db.CreateQueryDef(QueryTitle, strSQL)
End Sub
Sub prototype()
DoCmd.OpenQuery "All Field Types", acNormal, acEdit
End Sub
Function RefreshFields(DBID As String, QDBRecord As Variant) As Boolean
Dim rst As ADODB.Recordset
Dim strSQL As String
Dim i As Integer
Dim frmDataSheet As Form
Dim QDBRecordCopy() As Variant
'We have to lock the deleted field on the data manipulation form
Call DoCmd.OpenForm("frm_qdb_" + DBID, acDesign, "", "", acFormPropertySettings, acHidden)
Set frmDataSheet = Access.Application.Forms("frm_qdb_" + DBID)
Set qdbResponse = qdb.GetSchema(DBID)
Set rst = New ADODB.Recordset
strSQL = "SELECT qdbFields.* FROM qdbFields WHERE (((qdbFields.DBID)='" + DBID + "'));"
rst.Open strSQL, CurrentProject.Connection, adOpenForwardOnly, adLockOptimistic
Do While Not rst.EOF
If qdbResponse.documentElement.selectNodes("/*/table/fields/field[@id='" + rst("FID") + "']/@id").length = 0 Then
'This field does not exist any longer
rst("Deleted") = True
rst.Update
frmDataSheet.Controls(rst("Label")).Locked = True
End If
rst.MoveNext
Loop
rst.Close
DoCmd.Close acForm, "frm_qdb_" + DBID, acSaveYes
'Now we need to remove the field from the array so we can successfully make the edit
Dim lastfield As Integer
Dim firstfield As Integer
Dim j As Integer
lastfield = UBound(QDBRecord, 2)
firstfield = LBound(QDBRecord, 2)
ReDim QDBRecordCopy(1, lastfield)
j = 0
For i = firstfield To lastfield
If qdbResponse.documentElement.selectNodes("/*/table/fields/field[@id='" + CStr(QDBRecord(0, i)) + "']/@id").length <> 0 Then
QDBRecordCopy(0, j) = QDBRecord(0, i)
QDBRecordCopy(1, j) = QDBRecord(1, i)
j = j + 1
End If
Next i
If j = 0 Then
RefreshFields = False
Exit Function
Else
ReDim Preserve QDBRecordCopy(1, j - 1)
QDBRecord = QDBRecordCopy
RefreshFields = True
End If
End Function
Sub RegisterTable(strDBID As String, DBName As String)
Dim rst As ADODB.Recordset
Dim cat As ADOX.Catalog
Dim QDBTable As ADOX.Table
Set cat = New ADOX.Catalog
cat.ActiveConnection = CurrentProject.Connection
Set rst = New ADODB.Recordset
rst.Open "qdbTables", CurrentProject.Connection, adOpenForwardOnly, adLockOptimistic
rst.Find ("DBID = '" + strDBID + "'")
If rst.EOF Then
rst.AddNew
rst("Label") = DBName
rst("DBID") = strDBID
For Each QDBTable In cat.Tables
If "qdb_" + rst("DBID") = QDBTable.Name Then
rst("isLocal") = True
End If
Next QDBTable
On Error Resume Next
Err.Clear
rst.Update
If Err.Number <> 0 Then
rst.CancelUpdate
End If
Else
rst("isLocal") = True
End If
Call setTableDescription("qdb_" + rst("DBID"), DBName)
rst.Update
rst.Close
End Sub
Function getDBIDbyName(strUsername As String, strPassword As String, strDBName As String)
Dim i As Integer
i = qdb.Authenticate(strUsername, strPassword)
getDBIDbyName = qdb.FindDBByName(strDBName)
End Function
Function mirrorQDBTable(strDBID As String, strPrefix As String, strUsername As String, strPassword As String) As String
Dim NewTable As ADOX.Table
Dim strFIDs() As String
Dim fldField As ADOX.Column
Dim clist As String
Dim RecordNodeList As MSXML.IXMLDOMNodeList
Dim FieldNodeList As MSXML.IXMLDOMNodeList
Dim RecordIDFID As String
Dim rst As ADODB.Recordset
Dim i As Integer
Dim j As Integer
Dim strFID As String
Dim DBName As String
Dim cat As ADOX.Catalog
Set cat = New ADOX.Catalog
cat.ActiveConnection = CurrentProject.Connection
i = qdb.Authenticate(strUsername, strPassword)
For Each NewTable In cat.Tables
If strPrefix + strDBID = NewTable.Name Then
cat.Tables.Delete strPrefix + strDBID
End If
Next NewTable
Set NewTable = New ADOX.Table
NewTable.Name = strPrefix + strDBID
On Error GoTo noschema
Set qdbResponse = qdb.GetSchema(strDBID)
On Error GoTo 0
DBName = qdbResponse.documentElement.selectSingleNode("/*/table/name").Text
Set FieldNodeList = qdbResponse.documentElement.selectNodes("/*/table/fields/field")
clist = UpdateFieldsTable(strDBID, FieldNodeList, makeMSAccessObjectName(DBName))
ReDim strFIDs(FieldNodeList.length)
For i = 0 To FieldNodeList.length - 1
Set fldField = New ADOX.Column
strFIDs(i) = FieldNodeList(i).selectSingleNode("@id").Text
fldField.Name = strFIDs(i)
fldField.Attributes = adColNullable
Select Case FieldNodeList(i).selectSingleNode("@base_type").Text
Case "float"
fldField.Type = adDouble
Case "text"
fldField.Type = adLongVarWChar
Case "bool"
fldField.Attributes = fldField.Attributes And Not adColNullable
fldField.Type = adBoolean
Case "int64"
fldField.Type = adDate
Case "int32"
Select Case FieldNodeList(i).selectSingleNode("@field_type").Text
Case "recordid"
fldField.Type = adInteger
RecordIDFID = strFIDs(i)
Case Else
fldField.Type = adInteger
End Select
End Select
NewTable.Columns.Append fldField
Next i
Set fldField = New ADOX.Column
fldField.Name = UPDATE_ID_FIELDNAME
fldField.Type = adVarWChar
fldField.Attributes = adColNullable
NewTable.Columns.Append fldField
'Below describes bug
'http://support.microsoft.com/directory/article.asp?ID=KB;EN-US;Q272001
cat.Tables.Append NewTable
For i = 0 To FieldNodeList.length - 1
Select Case FieldNodeList(i).selectSingleNode("@base_type").Text
Case "text"
Select Case FieldNodeList(i).selectSingleNode("@field_type").Text
Case "file", "url"
setHyperLinkField "qdb_" + strDBID, FieldNodeList(i).selectSingleNode("@id").Text
Case Else
End Select
NewTable.Columns.Item(FieldNodeList(i).selectSingleNode("@id").Text).Properties("Jet OLEDB:Allow Zero Length").Value = True
End Select
NewTable.Columns.Item(FieldNodeList(i).selectSingleNode("@id").Text).Properties("Description").Value = FieldNodeList(i).selectSingleNode("label").Text
Next i
updateViewsTable strDBID
Set rst = New ADODB.Recordset
rst.Open strPrefix + strDBID, CurrentProject.Connection, adOpenForwardOnly, adLockPessimistic
Set qdbResponse = qdb.DoQuery(strDBID, "{'0'.CT.''}", clist, "", "")
Set RecordNodeList = qdbResponse.documentElement.selectNodes("/*/table/records/record")
Call SysCmd(acSysCmdInitMeter, "Downloading " + strDBID, RecordNodeList.length)
For i = 0 To RecordNodeList.length - 1
rst.AddNew
Call updateLocalRecord(strDBID, rst, RecordNodeList(i).selectNodes("f"), strFIDs(), FieldNodeList, RecordNodeList(i).selectSingleNode("f[@id=/*/table/fields/field[@role='modified']/@id]").Text)
rst.Update
Call SysCmd(acSysCmdUpdateMeter, i)
Next i
rst.Close
DoCmd.SetWarnings (False)
DoCmd.RunSQL ("CREATE UNIQUE INDEX " + RecordIDFID + " ON " + strPrefix + strDBID + " (" + RecordIDFID + ") WITH IGNORE NULL;")
DoCmd.SetWarnings (True)
Call RegisterTable(strDBID, DBName)
mirrorQDBTable = strPrefix + strDBID
Call SysCmd(acSysCmdRemoveMeter)
Exit Function
noschema:
If Err.Number = vbObjectError + 1 Then
Err.Raise vbObjectError + 1, "QuickBase Desktop", "Sorry we could not make a local copy of your database. You probably don't have full access to restriced fields."
Else
Err.Raise vbObjectError + 1, "QuickBase Desktop", Err.Description
End If
Exit Function
End Function
Sub copyTable(tblName As String, newTableName As String)
Dim cat As ADOX.Catalog
Set cat = New ADOX.Catalog
cat.ActiveConnection = CurrentProject.Connection
On Error Resume Next
cat.Tables.Delete newTableName
On Error GoTo 0
DoCmd.SetWarnings (False)
DoCmd.RunSQL ("SELECT [" + tblName + "].* INTO [" + newTableName + "] FROM " + tblName + ";")
DoCmd.SetWarnings (True)
End Sub
Function getRecordIDFID(DBID As String) As String
Dim rstRecords As ADODB.Recordset
Dim strSQL As String
Set rstRecords = New ADODB.Recordset
strSQL = "SELECT qdbFields.*, qdbFields.DBID FROM qdbFields WHERE (((qdbFields.DBID)='" + DBID + "'));"
rstRecords.Open strSQL, CurrentProject.Connection, adOpenForwardOnly
Do While Not rstRecords.EOF
If rstRecords("Field Type") = "recordid" Then
getRecordIDFID = rstRecords("FID")
rstRecords.Close
Set rstRecords = Nothing
Exit Function
End If
rstRecords.MoveNext
Loop
rstRecords.Close
Set rstRecords = Nothing
End Function
Sub DeleteRecords(DBID As String, strUsername As String, strPassword As String)
Dim strSQL As String
Dim rstRecords As ADODB.Recordset
Dim strRID As String
Dim strRecordIDFID As String
Dim i As Integer
Dim deletedRecords As Integer
Set rstRecords = New ADODB.Recordset
strRecordIDFID = getRecordIDFID(DBID)
i = qdb.Authenticate(strUsername, strPassword)
strSQL = "SELECT [orig_" + DBID + "].[" + strRecordIDFID + "] FROM orig_" + DBID + " LEFT JOIN qdb_" + DBID + " ON [orig_" + DBID + "].[" + strRecordIDFID + "] = [qdb_" + DBID + "].[" + strRecordIDFID + "] WHERE ((([qdb_" + DBID + "].[" + strRecordIDFID + "]) Is Null) AND (([orig_" + DBID + "].[" + strRecordIDFID + "]) Is Not Null));"
rstRecords.Open strSQL, CurrentProject.Connection, adOpenKeyset, adLockPessimistic
deletedRecords = rstRecords.RecordCount
If deletedRecords > 0 Then
If MsgBox("Are you sure you want to delete " + CStr(deletedRecords) + " records from www.quickbase.com?", vbOKCancel + vbQuestion, "QuickBase Delete Confirmation") <> vbOK Then
rstRecords.Close
Exit Sub
End If
End If
deletedRecords = 0
Do While Not rstRecords.EOF
'Delete the record from QuickBase
On Error Resume Next
Err.Clear
strRID = qdb.DeleteRecord(DBID, rstRecords(strRecordIDFID))
If Err.Number = 0 Then
deletedRecords = deletedRecords + 1
ElseIf Err.Number = 30 + vbObjectError Then
'The record is already gone
End If
On Error GoTo 0
rstRecords.Delete
rstRecords.MoveNext
Loop
rstRecords.Close
End Sub
Function ResolveConflict(qdb As QuickBaseClient, DBID As String, strRecordIDFID As String, rstOriginalRecords As ADODB.Recordset, rstModifiedRecords As ADODB.Recordset, QDBRecord() As Variant, ByRef QDBCurrentRecord() As Variant)
Dim fieldcounter As Integer
Dim resolvingConflict As Boolean
Dim lngLoop As Long
Dim strFieldName As String
Dim clist As String
Dim strCurrentUpdateID As String
Dim CurrentRecord As Collection
Dim ConflictingFIDs As Collection
DoEvents
resolvingConflict = True
lngLoop = 0
clist = getCompleteCList(DBID)
'Let's get the current record
strCurrentUpdateID = qdb.getRecordAsArray(DBID, clist, strRecordIDFID, rstModifiedRecords(strRecordIDFID), QDBCurrentRecord())
'Let's make a collection out of it
Set CurrentRecord = New Collection
For fieldcounter = 0 To UBound(QDBCurrentRecord, 2)
CurrentRecord.Add Key:=QDBCurrentRecord(0, fieldcounter), Item:=QDBCurrentRecord(1, fieldcounter)
Next fieldcounter
'Check to see if there is a conflict first
Set ConflictingFIDs = New Collection
For fieldcounter = 0 To UBound(QDBRecord, 2)
'Check all the pairs
If rstOriginalRecords(CStr(QDBRecord(0, fieldcounter))) = CurrentRecord(CStr(QDBRecord(0, fieldcounter))) _
Or (IsNull(rstOriginalRecords(CStr(QDBRecord(0, fieldcounter)))) _
And IsNull(CurrentRecord(CStr(QDBRecord(0, fieldcounter))))) Then
'If we're here we do not have a record conflict!
ElseIf rstModifiedRecords(CStr(QDBRecord(0, fieldcounter))) = CurrentRecord(CStr(QDBRecord(0, fieldcounter))) _
Or (IsNull(rstModifiedRecords(CStr(QDBRecord(0, fieldcounter)))) _
And IsNull(CurrentRecord(CStr(QDBRecord(0, fieldcounter))))) Then
'If we're here we do not have a record conflict!
ElseIf rstModifiedRecords(CStr(QDBRecord(0, fieldcounter))) = rstOriginalRecords(CStr(QDBRecord(0, fieldcounter))) _
Or (IsNull(rstModifiedRecords(CStr(QDBRecord(0, fieldcounter)))) _
And IsNull(rstOriginalRecords(CStr(QDBRecord(0, fieldcounter))))) Then
'If we're here we do not have a record conflict!
Else
'We have a record conflict
'So let's record the FIDs in a collection
ConflictingFIDs.Add Item:=CStr(QDBRecord(0, fieldcounter)), Key:=CStr(QDBRecord(0, fieldcounter))
End If
Next fieldcounter
If ConflictingFIDs.Count = 0 Then 'There is no conflict!
ResolveConflict = strCurrentUpdateID
Exit Function
End If
MsgBox "We'll show you what you had when you last synched," + vbCrLf + _
"your changes and what was changed up at www.quickbase.com", vbOKOnly, "Replication Conflict"
Call DoCmd.OpenForm("conflict_qdb_" + DBID, acNormal, "", "", acFormPropertySettings, acHidden)
'Set all the control values here
For fieldcounter = 0 To rstOriginalRecords.Fields.Count - 1
strFieldName = rstOriginalRecords(fieldcounter).Name
strFieldName = Mid(strFieldName, InStr(strFieldName, ".") + 1)
If strFieldName <> UPDATE_ID_FIELDNAME Then
Access.Forms("conflict_qdb_" + DBID).Controls("orig_" + strFieldName).Value = rstOriginalRecords(fieldcounter)
End If
Next fieldcounter
For fieldcounter = 0 To rstModifiedRecords.Fields.Count - 1
strFieldName = rstModifiedRecords(fieldcounter).Name
strFieldName = Mid(strFieldName, InStr(strFieldName, ".") + 1)
If strFieldName <> UPDATE_ID_FIELDNAME Then
Access.Forms("conflict_qdb_" + DBID).Controls("proposed_" + strFieldName).Value = rstModifiedRecords(fieldcounter)
End If
Next fieldcounter
For fieldcounter = 0 To UBound(QDBCurrentRecord, 2)
Access.Forms("conflict_qdb_" + DBID).Controls("current_" + QDBCurrentRecord(0, fieldcounter)).Value = QDBCurrentRecord(1, fieldcounter)
Next fieldcounter
For fieldcounter = 1 To ConflictingFIDs.Count
Access.Forms("conflict_qdb_" + DBID).Controls("proposed_" + ConflictingFIDs(fieldcounter)).Locked = False
Access.Forms("conflict_qdb_" + DBID).Controls("proposed_" + ConflictingFIDs(fieldcounter)).BackColor = 16777215
Next fieldcounter
On Error GoTo 0
'Now make the form visible
DoCmd.Hourglass False
Access.Forms("conflict_qdb_" + DBID).Visible = True
'loop and wait until the form is closed or hidden
Do While resolvingConflict
DoEvents
lngLoop = lngLoop + 1
If lngLoop > 1000 Then
lngLoop = 0
If SysCmd(acSysCmdGetObjectState, acForm, "conflict_qdb_" + DBID) = 0 Then
'form is no longer loaded
strCurrentUpdateID = "-" + strCurrentUpdateID
resolvingConflict = False
ElseIf Access.Forms("conflict_qdb_" + DBID).Visible = False Then
'form is no longer visible
resolvingConflict = False
'Need to pull out the changes before we close the form
For fieldcounter = 0 To UBound(QDBRecord, 2)
QDBRecord(1, fieldcounter) = Access.Forms("conflict_qdb_" + DBID).Controls("proposed_" + CStr(QDBRecord(0, fieldcounter))).Value
Next fieldcounter
DoCmd.Close acForm, "conflict_qdb_" + DBID
End If
End If
Loop
DoCmd.Hourglass True
ResolveConflict = strCurrentUpdateID
End Function
Sub downloadAllAttachedFiles(DBID As String, strUsername As String, strPassword As String)
Dim rstFields As ADODB.Recordset
Dim rstModifiedRecords As ADODB.Recordset
Dim strSQL As String
Dim strFieldName As String
Dim strFileName As String
Dim strTempFilename As String
Dim i As Integer
Call qdb.Authenticate(strUsername, strPassword)
Call qdb.GetDBInfo(DBID)
Set rstFields = New ADODB.Recordset
strSQL = "SELECT qdbFields.FID FROM qdbFields WHERE (((qdbFields.DBID)='" + DBID + "') AND ((qdbFields.Deleted)=False) AND ((qdbFields.[Field Type]) = 'File'));"
rstFields.Open strSQL, CurrentProject.Connection, adOpenKeyset
Set rstModifiedRecords = New ADODB.Recordset
strSQL = "SELECT [qdb_" + DBID + "].* FROM qdb_" + DBID + ";"
rstModifiedRecords.Open strSQL, CurrentProject.Connection, adOpenKeyset, adLockReadOnly
i = SysCmd(acSysCmdInitMeter, "Downloading files from " + DBID, rstModifiedRecords.RecordCount)
On Error Resume Next
MkDir Application.CurrentProject.Path + "\" + DBID
On Error GoTo 0
i = 0
Do While Not rstModifiedRecords.EOF
rstFields.Requery
rstFields.MoveFirst
Do While Not rstFields.EOF
strFieldName = rstFields(0)
If IsNull(rstModifiedRecords(strFieldName)) Then
Else
strFileName = rstModifiedRecords(strFieldName)
If InStr(strFileName, "file://") Then
strFileName = Left(strFileName, InStr(strFileName, "#") - 1)
downloadAttachedFile DBID, strFileName
End If
End If
rstFields.MoveNext
Loop
rstModifiedRecords.MoveNext
Call SysCmd(acSysCmdUpdateMeter, i)
i = i + 1
Loop
Call SysCmd(acSysCmdRemoveMeter)
End Sub
Sub updateLocalRecord(strDBID As String, rst As ADODB.Recordset, CellNodeList As MSXML.IXMLDOMNodeList, strFIDs() As String, FieldNodeList As MSXML.IXMLDOMNodeList, UpdateID As String)
Dim j As Integer
Dim strFileName As String
For j = 0 To CellNodeList.length - 1
Select Case rst(strFIDs(j)).Type
Case adDate
If CellNodeList(j).nodeTypedValue <> "" Then
rst(strFIDs(j)) = qdb.int64ToDate(CellNodeList(j).nodeTypedValue) '+ (MinutesGMTOffset / (60 * 24))
End If
Case adDouble
If CellNodeList(j).nodeTypedValue = "" Then
rst(strFIDs(j)) = Null
Else
rst(strFIDs(j)) = CDbl(CellNodeList(j).nodeTypedValue)
End If
Case Else
'Somehow we have to determine if this is a URL of file field
Select Case FieldNodeList(j).selectSingleNode("@field_type").Text
Case "file"
If CellNodeList(j).nodeTypedValue <> "" Then
If InStr(CellNodeList(j).nodeTypedValue, ":") > 0 Then
'We have a URL
rst(strFIDs(j)) = CellNodeList(j).nodeTypedValue + "#" + CellNodeList(j).nodeTypedValue + "##"
Else
'We have a file
rst(strFIDs(j)) = CellNodeList(j).nodeTypedValue + "#file://" + Application.CurrentProject.Path + "\" + strDBID + "\" + CellNodeList(j).nodeTypedValue + "##"
'Need to download the file just in case it has changed
downloadAttachedFile strDBID, CellNodeList(j).nodeTypedValue
End If
End If
Case "url"
rst(strFIDs(j)) = CellNodeList(j).nodeTypedValue + "#" + CellNodeList(j).nodeTypedValue + "##"
Case Else
rst(strFIDs(j)) = Replace(CellNodeList(j).nodeTypedValue, Chr(10), vbCrLf)
End Select
End Select
Next j
rst(UPDATE_ID_FIELDNAME) = UpdateID
End Sub
Sub UpdateRecords(DBID As String, strUsername As String, strPassword As String)
Dim strSQL As String
Dim rstOriginalRecords As ADODB.Recordset
Dim rstModifiedRecords As ADODB.Recordset
Dim rstFields As ADODB.Recordset
Dim fldLoop As ADODB.Field
Dim QDBRecord() As Variant
Dim QDBCurrentRecord() As Variant
Dim strRID As String
Dim strUpdateID As String
Dim strFieldName As String
Dim varOriginalValue As Variant