ipp_quickbase_devkit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +87 -0
- data/README.rdoc +112 -0
- data/doc/QuickBaseClient.rb.htm +1896 -0
- data/doc/ReleaseNotes.txt +43 -0
- data/doc/qbc.makeCSVFile.qbc +4 -0
- data/doc/qbc.makeCSVFile.rb +4 -0
- data/doc/quickbase_adapter.rb.htm +399 -0
- data/examples/cookbookfiles/QuickBaseAPICookbook.html +2590 -0
- data/examples/cookbookfiles/addChangeRemoveUserRole.rb +21 -0
- data/examples/cookbookfiles/addOrEditRecord.rb +10 -0
- data/examples/cookbookfiles/application_object.rb +55 -0
- data/examples/cookbookfiles/applyRubyFormulas.rb +10 -0
- data/examples/cookbookfiles/average.rb +27 -0
- data/examples/cookbookfiles/backupApplication.rb +8 -0
- data/examples/cookbookfiles/cacheSchemas.rb +53 -0
- data/examples/cookbookfiles/calculateRunningTotals.rb +11 -0
- data/examples/cookbookfiles/copyrecords.rb +73 -0
- data/examples/cookbookfiles/count.rb +26 -0
- data/examples/cookbookfiles/createRecordNavigatorHTML.rb +42 -0
- data/examples/cookbookfiles/createReportDashboard.rb +35 -0
- data/examples/cookbookfiles/createTable.rb +12 -0
- data/examples/cookbookfiles/deviation.rb +25 -0
- data/examples/cookbookfiles/downloadCookbook.rb +97 -0
- data/examples/cookbookfiles/downloadFile.rb +10 -0
- data/examples/cookbookfiles/downloadFilesToFolder.rb +81 -0
- data/examples/cookbookfiles/downloadToTextFile.rb +64 -0
- data/examples/cookbookfiles/dumpSchema.rb +11 -0
- data/examples/cookbookfiles/duplicateRecord.rb +8 -0
- data/examples/cookbookfiles/dynamicMethods.rb +33 -0
- data/examples/cookbookfiles/editRecords.rb +15 -0
- data/examples/cookbookfiles/findJohnsLast10Records.rb +17 -0
- data/examples/cookbookfiles/findRubyRecords.rb +17 -0
- data/examples/cookbookfiles/formatCurrency.rb +24 -0
- data/examples/cookbookfiles/formatDate.rb +10 -0
- data/examples/cookbookfiles/formatDuration.rb +27 -0
- data/examples/cookbookfiles/formatPercent.rb +24 -0
- data/examples/cookbookfiles/getAllValuesForFields.rb +18 -0
- data/examples/cookbookfiles/getAppDTMInfo.rb +29 -0
- data/examples/cookbookfiles/getApplicationVariable.rb +5 -0
- data/examples/cookbookfiles/getChildTableDBID.rb +11 -0
- data/examples/cookbookfiles/getColumnListForReport.rb +6 -0
- data/examples/cookbookfiles/getFieldChoices.rb +13 -0
- data/examples/cookbookfiles/getFieldIDs.rb +6 -0
- data/examples/cookbookfiles/getFieldNames.rb +6 -0
- data/examples/cookbookfiles/getLastModTime.rb +8 -0
- data/examples/cookbookfiles/getLastRecModTime.rb +8 -0
- data/examples/cookbookfiles/getNumRecords.rb +8 -0
- data/examples/cookbookfiles/getNumTables.rb +4 -0
- data/examples/cookbookfiles/getRecord.rb +5 -0
- data/examples/cookbookfiles/getRecordDisplayURL.rb +13 -0
- data/examples/cookbookfiles/getRecordsAddedToday.rb +20 -0
- data/examples/cookbookfiles/getRecordsAsJSON.rb +6 -0
- data/examples/cookbookfiles/getReportNames.rb +25 -0
- data/examples/cookbookfiles/getRoleInfo.rb +48 -0
- data/examples/cookbookfiles/getServerStatus.rb +11 -0
- data/examples/cookbookfiles/getSortListForReport.rb +6 -0
- data/examples/cookbookfiles/getTableIDs.rb +6 -0
- data/examples/cookbookfiles/getTableName.rb +8 -0
- data/examples/cookbookfiles/getTableNames.rb +25 -0
- data/examples/cookbookfiles/getTimeCreated.rb +8 -0
- data/examples/cookbookfiles/getTimeInMilliseconds.rb +5 -0
- data/examples/cookbookfiles/getUserInfo.rb +26 -0
- data/examples/cookbookfiles/getUserRole.rb +15 -0
- data/examples/cookbookfiles/intranet.rb +101 -0
- data/examples/cookbookfiles/isAverageField.rb +17 -0
- data/examples/cookbookfiles/isDbidString.rb +8 -0
- data/examples/cookbookfiles/isTotalField.rb +16 -0
- data/examples/cookbookfiles/iterateDBPages.rb +8 -0
- data/examples/cookbookfiles/iterateFilteredRecords.rb +12 -0
- data/examples/cookbookfiles/iterateJoinRecords.rb +68 -0
- data/examples/cookbookfiles/iterateRecordInfos.rb +8 -0
- data/examples/cookbookfiles/iterateRecords.rb +23 -0
- data/examples/cookbookfiles/iterateSummaryRecords.rb +13 -0
- data/examples/cookbookfiles/iterateUnionRecords.rb +38 -0
- data/examples/cookbookfiles/listAccessibleApplications.rb +6 -0
- data/examples/cookbookfiles/logRequestAndResponseXML.rb +8 -0
- data/examples/cookbookfiles/lookupFieldPropertyByName.rb +62 -0
- data/examples/cookbookfiles/lookupFieldTypeByName.rb +10 -0
- data/examples/cookbookfiles/makeCSVFile.rb +4 -0
- data/examples/cookbookfiles/makeSlideShow.rb +42 -0
- data/examples/cookbookfiles/makerecs.rb +64 -0
- data/examples/cookbookfiles/max.rb +26 -0
- data/examples/cookbookfiles/min.rb +26 -0
- data/examples/cookbookfiles/percent.rb +29 -0
- data/examples/cookbookfiles/printChildElements.rb +54 -0
- data/examples/cookbookfiles/printNewRecords.rb +12 -0
- data/examples/cookbookfiles/processRESTRequest.rb +21 -0
- data/examples/cookbookfiles/provisionAndInviteNewUser.rb +13 -0
- data/examples/cookbookfiles/purgeRecords.rb +15 -0
- data/examples/cookbookfiles/quickbase_adapter.rb.htm +397 -0
- data/examples/cookbookfiles/quickbase_record_finder.zip +0 -0
- data/examples/cookbookfiles/recordAndFieldIterator.rb +24 -0
- data/examples/cookbookfiles/runImport.rb +9 -0
- data/examples/cookbookfiles/runQuickBaseTwitterConnector.rb +41 -0
- data/examples/cookbookfiles/sendToQuickBase.rb +33 -0
- data/examples/cookbookfiles/setDBvar.rb +6 -0
- data/examples/cookbookfiles/showRequestAndResponseXML.rb +8 -0
- data/examples/cookbookfiles/sqlQuery.rb +11 -0
- data/examples/cookbookfiles/stopOnError.rb +10 -0
- data/examples/cookbookfiles/sum.rb +26 -0
- data/examples/cookbookfiles/twitterFromQuickBase.rb +42 -0
- data/examples/cookbookfiles/twitterWithQuickBase.rb +36 -0
- data/examples/cookbookfiles/uploadCSVData.rb +20 -0
- data/examples/cookbookfiles/uploadExcelData.rb +22 -0
- data/examples/cookbookfiles/uploadFileEveryHour.rb +18 -0
- data/examples/cookbookfiles/uploadFileIntoNewRecord.rb +8 -0
- data/examples/cookbookfiles/uploadFilesFromFolder.exe +0 -0
- data/examples/cookbookfiles/uploadFilesFromFolder.rb +69 -0
- data/examples/cookbookfiles/useCompanyURL.rb +12 -0
- data/examples/cookbookfiles/userRoles.rb +49 -0
- data/examples/cookbookfiles/watchCommunityForum.rb +5 -0
- data/examples/cookbookfiles/wikifyTable.rb +29 -0
- data/examples/cookbookfiles/xmlShortcuts.rb +33 -0
- data/examples/pmp/app/controllers/application.rb +7 -0
- data/examples/pmp/app/controllers/contacts_controller.rb +8 -0
- data/examples/pmp/app/controllers/document_library_controller.rb +2 -0
- data/examples/pmp/app/controllers/issues_controller.rb +5 -0
- data/examples/pmp/app/controllers/projects_controller.rb +22 -0
- data/examples/pmp/app/controllers/resources_controller.rb +2 -0
- data/examples/pmp/app/controllers/tasks_controller.rb +13 -0
- data/examples/pmp/app/controllers/time_cards_controller.rb +5 -0
- data/examples/pmp/app/helpers/application_helper.rb +3 -0
- data/examples/pmp/app/helpers/contacts_helper.rb +2 -0
- data/examples/pmp/app/helpers/document_library_helper.rb +2 -0
- data/examples/pmp/app/helpers/issues_helper.rb +2 -0
- data/examples/pmp/app/helpers/projects_helper.rb +2 -0
- data/examples/pmp/app/helpers/resources_helper.rb +2 -0
- data/examples/pmp/app/helpers/tasks_helper.rb +2 -0
- data/examples/pmp/app/helpers/time_cards_helper.rb +2 -0
- data/examples/pmp/app/models/contacts.rb +26 -0
- data/examples/pmp/app/models/document_library.rb +2 -0
- data/examples/pmp/app/models/issues.rb +6 -0
- data/examples/pmp/app/models/projects.rb +26 -0
- data/examples/pmp/app/models/resources.rb +2 -0
- data/examples/pmp/app/models/tasks.rb +12 -0
- data/examples/pmp/app/models/time_cards.rb +7 -0
- data/examples/pmp/app/schemas/contacts.xml +1 -0
- data/examples/pmp/app/schemas/document_library.xml +1 -0
- data/examples/pmp/app/schemas/issues.xml +1 -0
- data/examples/pmp/app/schemas/pmp.xml +1 -0
- data/examples/pmp/app/schemas/projects.xml +1 -0
- data/examples/pmp/app/schemas/readme.txt +8 -0
- data/examples/pmp/app/schemas/resources.xml +1 -0
- data/examples/pmp/app/schemas/tasks.xml +1 -0
- data/examples/pmp/app/schemas/time_cards.xml +1 -0
- data/examples/pmp/app/views/contacts/companies.rhtml +31 -0
- data/examples/pmp/app/views/contacts/project_contacts.rhtml +31 -0
- data/examples/pmp/app/views/issues/filter_issues.rhtml +26 -0
- data/examples/pmp/app/views/layouts/application.rhtml +56 -0
- data/examples/pmp/app/views/projects/all_projects.rhtml +33 -0
- data/examples/pmp/app/views/projects/home.rhtml +11 -0
- data/examples/pmp/app/views/projects/my_open_projects.rhtml +27 -0
- data/examples/pmp/app/views/projects/open_projects.rhtml +44 -0
- data/examples/pmp/app/views/projects/project_sorted_by_company.rhtml +40 -0
- data/examples/pmp/app/views/projects/projects_sorted_by_priority.rhtml +30 -0
- data/examples/pmp/app/views/projects/updated_projects.rhtml +0 -0
- data/examples/pmp/app/views/tasks/all_tasks.rhtml +27 -0
- data/examples/pmp/app/views/tasks/search.rhtml +23 -0
- data/examples/pmp/app/views/tasks/search2.rhtml +23 -0
- data/examples/pmp/app/views/tasks/search3.rhtml +23 -0
- data/examples/pmp/app/views/time_cards/summary.rhtml +38 -0
- data/examples/pmp/config/boot.rb +45 -0
- data/examples/pmp/config/database.yml +30 -0
- data/examples/pmp/config/environment.rb +60 -0
- data/examples/pmp/config/environments/development.rb +21 -0
- data/examples/pmp/config/environments/production.rb +18 -0
- data/examples/pmp/config/environments/test.rb +19 -0
- data/examples/pmp/config/routes.rb +23 -0
- data/examples/pmp/db/migrate/001_create_projects.rb +10 -0
- data/examples/pmp/db/migrate/002_create_tasks.rb +10 -0
- data/examples/pmp/db/migrate/003_create_issues.rb +10 -0
- data/examples/pmp/db/migrate/004_create_document_libraries.rb +10 -0
- data/examples/pmp/db/migrate/005_create_resources.rb +10 -0
- data/examples/pmp/db/migrate/006_create_time_cards.rb +10 -0
- data/examples/pmp/db/migrate/007_create_contacts.rb +10 -0
- data/examples/pmp/public/404.html +30 -0
- data/examples/pmp/public/500.html +30 -0
- data/examples/pmp/public/app.index.html +277 -0
- data/examples/pmp/public/dispatch.cgi +10 -0
- data/examples/pmp/public/dispatch.fcgi +24 -0
- data/examples/pmp/public/dispatch.rb +10 -0
- data/examples/pmp/public/favicon.ico +0 -0
- data/examples/pmp/public/images/rails.png +0 -0
- data/examples/pmp/public/javascripts/application.js +2 -0
- data/examples/pmp/public/javascripts/controls.js +833 -0
- data/examples/pmp/public/javascripts/dragdrop.js +942 -0
- data/examples/pmp/public/javascripts/effects.js +1088 -0
- data/examples/pmp/public/javascripts/prototype.js +2515 -0
- data/examples/pmp/public/robots.txt +1 -0
- data/examples/pmp/test/fixtures/contacts.yml +5 -0
- data/examples/pmp/test/fixtures/document_libraries.yml +5 -0
- data/examples/pmp/test/fixtures/issues.yml +5 -0
- data/examples/pmp/test/fixtures/projects.yml +5 -0
- data/examples/pmp/test/fixtures/resources.yml +5 -0
- data/examples/pmp/test/fixtures/tasks.yml +5 -0
- data/examples/pmp/test/fixtures/time_cards.yml +5 -0
- data/examples/pmp/test/functional/contacts_controller_test.rb +18 -0
- data/examples/pmp/test/functional/document_library_controller_test.rb +18 -0
- data/examples/pmp/test/functional/issues_controller_test.rb +18 -0
- data/examples/pmp/test/functional/projects_controller_test.rb +18 -0
- data/examples/pmp/test/functional/resources_controller_test.rb +18 -0
- data/examples/pmp/test/functional/tasks_controller_test.rb +18 -0
- data/examples/pmp/test/functional/time_cards_controller_test.rb +18 -0
- data/examples/pmp/test/test_helper.rb +28 -0
- data/examples/pmp/test/unit/contacts_test.rb +10 -0
- data/examples/pmp/test/unit/document_library_test.rb +10 -0
- data/examples/pmp/test/unit/issues_test.rb +10 -0
- data/examples/pmp/test/unit/projects_test.rb +10 -0
- data/examples/pmp/test/unit/resources_test.rb +10 -0
- data/examples/pmp/test/unit/tasks_test.rb +10 -0
- data/examples/pmp/test/unit/time_cards_test.rb +10 -0
- data/lib/QuickBaseClient.rb +5054 -0
- data/lib/QuickBaseCommandLineClient.rb +401 -0
- data/lib/QuickBaseContactsAppBuilder.rb +419 -0
- data/lib/QuickBaseEmailer.rb +334 -0
- data/lib/QuickBaseEventNotifier.rb +592 -0
- data/lib/QuickBaseMisc.rb +96 -0
- data/lib/QuickBaseObjects.rb +566 -0
- data/lib/QuickBaseRSSGenerator.rb +286 -0
- data/lib/QuickBaseTextData.rb +545 -0
- data/lib/QuickBaseTwitterConnector.rb +300 -0
- data/lib/QuickBaseWebClient.rb +126 -0
- data/lib/WorkPlaceClient.rb +45 -0
- data/lib/qbc.makeCSVFile.qbc +4 -0
- data/lib/qbc.makeCSVFile.rb +17 -0
- data/lib/quickbase_adapter.rb +320 -0
- data/lib/runFieldEntryDialog.rb +151 -0
- data/lib/runOfflineFieldEntryDialog.rb +203 -0
- data/rakefile +100 -0
- data/test/run_tests.bat +7 -0
- data/test/spec_all_tests.rb +13 -0
- data/test/spec_smoke_tests.rb +58 -0
- data/test/spec_workplace_addrecord_test.rb +46 -0
- data/test/spec_workplace_base_test.rb +57 -0
- data/test/spec_workplace_editrecord_test.rb +38 -0
- data/test/spec_workplace_json_test.rb +38 -0
- data/test/spec_workplace_objects_test.rb +39 -0
- data/test/spec_workplace_smoke_tests.rb +45 -0
- metadata +353 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#--#####################################################################
|
|
2
|
+
# Copyright (c) 2009 Gareth Lewis and Intuit, Inc.
|
|
3
|
+
#
|
|
4
|
+
# All rights reserved. This program and the accompanying materials
|
|
5
|
+
# are made available under the terms of the Eclipse Public License v1.0
|
|
6
|
+
# which accompanies this distribution, and is available at
|
|
7
|
+
# http://www.opensource.org/licenses/eclipse-1.0.php
|
|
8
|
+
#
|
|
9
|
+
# Contributors:
|
|
10
|
+
# Gareth Lewis - Initial contribution.
|
|
11
|
+
# Intuit Partner Platform.
|
|
12
|
+
#++#####################################################################
|
|
13
|
+
|
|
14
|
+
require "QuickBaseClient"
|
|
15
|
+
require "net/smtp"
|
|
16
|
+
|
|
17
|
+
#require 'tls_smtp'
|
|
18
|
+
|
|
19
|
+
module QuickBase
|
|
20
|
+
|
|
21
|
+
# Simple class to read data from QuickBase and email it using the SMTP client that
|
|
22
|
+
# comes with Ruby. This class should handle sending emails to people in the same domain
|
|
23
|
+
# as you, e.g. when your 'from' and 'to' email addresses all end in '@ourcompany.com'.
|
|
24
|
+
# To send emails outside your domain, it's likely that you will have to override
|
|
25
|
+
# the sendUnauthenticatedEmail() method or the sendAuthenticatedEmail() method
|
|
26
|
+
# and add authentication acceptable to your email server.
|
|
27
|
+
#
|
|
28
|
+
# To send email via GMail's server:
|
|
29
|
+
#
|
|
30
|
+
# * download http://s3.amazonaws.com/drawohara.com.ruby/tls_smtp.rb
|
|
31
|
+
# * comment out the 'require "net/smtp"' line above
|
|
32
|
+
# * uncomment the #require 'tls_smtp' line
|
|
33
|
+
# * use smtp.gmail.com as the mail server
|
|
34
|
+
#
|
|
35
|
+
class Emailer
|
|
36
|
+
|
|
37
|
+
attr_writer :from, :to, :subject, :message
|
|
38
|
+
attr_writer :mailServer, :mailPort, :fromDomain, :authenticationType
|
|
39
|
+
attr_writer :emailUsername, :emailPassword
|
|
40
|
+
|
|
41
|
+
def initialize(username,password, emailUsername=nil, emailPassword=nil)
|
|
42
|
+
@username,@password = username,password
|
|
43
|
+
@emailUsername, @emailPassword=emailUsername, emailPassword
|
|
44
|
+
@emailUsername = @username if @emailUsername.nil?
|
|
45
|
+
@emailPassword = @password if @emailPassword.nil?
|
|
46
|
+
@mailPort = 25
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def sendBccEmail(from, to, bcc, subject, message, mailServer, mailPort, fromDomain)
|
|
50
|
+
@bcc = bcc.dup
|
|
51
|
+
sendEmail(from, to, subject, message, mailServer, mailPort, fromDomain)
|
|
52
|
+
@bcc = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def sendEmail(from, to, subject, message, mailServer, mailPort, fromDomain)
|
|
56
|
+
|
|
57
|
+
@from = from if from
|
|
58
|
+
@to = to if to
|
|
59
|
+
@subject = subject if subject
|
|
60
|
+
@message = message if message
|
|
61
|
+
@mailServer = mailServer if mailServer
|
|
62
|
+
@mailPort = mailPort if mailPort
|
|
63
|
+
@fromDomain = fromDomain if fromDomain
|
|
64
|
+
|
|
65
|
+
if validateEmailData
|
|
66
|
+
email = buildEmail(@from,@to,@subject,@message)
|
|
67
|
+
@to = @to + @bcc if @bcc
|
|
68
|
+
if @authenticationType.nil? or @authenticationType == ""
|
|
69
|
+
sendUnauthenticatedEmail(email,@from,@to,@mailServer, @mailPort, @fromDomain)
|
|
70
|
+
else
|
|
71
|
+
sendAuthenticatedEmail(email,
|
|
72
|
+
@from,
|
|
73
|
+
@to,
|
|
74
|
+
@mailServer,
|
|
75
|
+
@mailPort,
|
|
76
|
+
@fromDomain,
|
|
77
|
+
@emailUsername,
|
|
78
|
+
@emailPassword,
|
|
79
|
+
@authenticationType )
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def sendUnauthenticatedEmail(email,from,to,mailServer, mailPort, fromDomain)
|
|
86
|
+
begin
|
|
87
|
+
Net::SMTP::start(mailServer,mailPort,fromDomain) { |smtp|
|
|
88
|
+
smtp.send_message(email,from,to)
|
|
89
|
+
}
|
|
90
|
+
rescue StandardError => error
|
|
91
|
+
raise "Error sending email: #{error}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def sendAuthenticatedEmail(email,from,to,mailServer, mailPort, fromDomain,
|
|
96
|
+
emailUsername,emailPassword,authenticationType)
|
|
97
|
+
begin
|
|
98
|
+
Net::SMTP::start(mailServer,mailPort,fromDomain,emailUsername,emailPassword,authenticationType) { |smtp|
|
|
99
|
+
smtp.send_message(email,from,to)
|
|
100
|
+
}
|
|
101
|
+
rescue StandardError => error
|
|
102
|
+
raise "Error sending email: #{error}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def validateEmailData
|
|
107
|
+
|
|
108
|
+
raise "'@from' email address is missing" if @from.nil?
|
|
109
|
+
raise "'@to' email address is missing" if @to.nil?
|
|
110
|
+
@subject = "" if @subject.nil?
|
|
111
|
+
@message = "" if @message.nil?
|
|
112
|
+
raise "'@mailServer' is missing" if @mailServer.nil?
|
|
113
|
+
raise "'@mailPort' is missing" if @mailPort.nil?
|
|
114
|
+
|
|
115
|
+
raise "'@mailServer' must be a String" if not @mailServer.is_a?(String)
|
|
116
|
+
|
|
117
|
+
if @mailServer and @fromDomain.nil?
|
|
118
|
+
@fromDomain = @mailServer
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
raise "'@fromDomain' must be a String" if not @fromDomain.is_a?(String)
|
|
122
|
+
|
|
123
|
+
if not @to.is_a?(Array)
|
|
124
|
+
raise "'@to' must be an Array"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
@to.each { |toAddress|
|
|
128
|
+
if not toAddress.index('@')
|
|
129
|
+
raise "'#{toAddress}' is not a valid email address"
|
|
130
|
+
end
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if @bcc
|
|
134
|
+
if not @bcc.is_a?(Array)
|
|
135
|
+
raise "'@bcc' must be an Array"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
@bcc.each { |toAddress|
|
|
139
|
+
if not toAddress.index('@')
|
|
140
|
+
raise "'#{toAddress}' is not a valid email address"
|
|
141
|
+
end
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if not @from.is_a?(String)
|
|
146
|
+
raise "'@from' must be an String"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
if not @from.index('@')
|
|
150
|
+
raise "'@from' is not a valid email address"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if not @mailPort.is_a?(Numeric)
|
|
154
|
+
raise "'@mailPort' must be a positive number"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
raise "'@subject' must be a String" if not @subject.is_a?(String)
|
|
158
|
+
raise "'@message' must be a String" if not @message.is_a?(String)
|
|
159
|
+
|
|
160
|
+
if @authenticationType and @authenticationType != ""
|
|
161
|
+
if not ["login","cram-md5","plain"].include?(@authenticationType)
|
|
162
|
+
raise "'@authenticationType' #{@authenticationType} is invalid."
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def buildEmail(from,to,subject,message)
|
|
170
|
+
from.strip!
|
|
171
|
+
email = "From:#{from}\n"
|
|
172
|
+
email << "Date:#{Time.now}\n"
|
|
173
|
+
to.each{|toAddress|
|
|
174
|
+
toAddress.strip!
|
|
175
|
+
email << "To:#{toAddress}\n"
|
|
176
|
+
}
|
|
177
|
+
email << "Subject:#{subject}\n"
|
|
178
|
+
email << "#{message}\n"
|
|
179
|
+
email
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def readEmailServerConfigFromQuickBase(dbid,fieldNames,fids,query=nil,qname=nil,qid=nil)
|
|
183
|
+
emailConfiguration = nil
|
|
184
|
+
if not fids.is_a?(Hash)
|
|
185
|
+
raise "'fids' must be a Hash of email server configuration field names and their QuickBase field id's"
|
|
186
|
+
end
|
|
187
|
+
if not fieldNames.is_a?(Hash)
|
|
188
|
+
raise "'fieldNames' must be a Hash of email configuration fields and their field names in QuickBase"
|
|
189
|
+
end
|
|
190
|
+
clist = ""
|
|
191
|
+
["mailServer","mailPort","fromDomain","authenticationType"].each{|fieldName|
|
|
192
|
+
if fids[fieldName]
|
|
193
|
+
clist << fids[fieldName]
|
|
194
|
+
clist << "." unless fieldName == "authenticationType"
|
|
195
|
+
else
|
|
196
|
+
raise "'#{fieldName}' fid entry is missing from the 'fids' Hash"
|
|
197
|
+
end
|
|
198
|
+
if not fieldNames[fieldName]
|
|
199
|
+
raise "'#{fieldName}' QuickBase field name entry is missing from the 'fieldNames' Hash"
|
|
200
|
+
end
|
|
201
|
+
}
|
|
202
|
+
qbc = QuickBase::Client.new(@username,@password)
|
|
203
|
+
if qbc and qbc.requestSucceeded
|
|
204
|
+
qbc.getSchema(dbid)
|
|
205
|
+
if qbc.requestSucceeded
|
|
206
|
+
emailConfiguration = qbc.getAllValuesForFields(dbid,fieldNames.values,query,qname,qid,clist,nil,"structured","num-1")
|
|
207
|
+
emailConfiguration = nil if emailConfiguration.length == 0
|
|
208
|
+
else
|
|
209
|
+
raise "Error accessing QuickBase table '#{dbid}'."
|
|
210
|
+
end
|
|
211
|
+
else
|
|
212
|
+
raise "Error accessing QuickBase. Please check your internet connection, username (#{@username}) and password (#{@password})"
|
|
213
|
+
end
|
|
214
|
+
qbc.signOut if qbc
|
|
215
|
+
emailConfiguration
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def readEmailsToSendFromQuickBase(dbid,fieldNames,fids,query=nil,qname=nil,qid=nil)
|
|
219
|
+
emailMessages = nil
|
|
220
|
+
if not fids.is_a?(Hash)
|
|
221
|
+
raise "'fids' must be a Hash of email field names and their QuickBase field id's"
|
|
222
|
+
end
|
|
223
|
+
if not fieldNames.is_a?(Hash)
|
|
224
|
+
raise "'fieldNames' must be a Hash of email fields and their field names in QuickBase"
|
|
225
|
+
end
|
|
226
|
+
clist = ""
|
|
227
|
+
["from","to","subject","message"].each{|fieldName|
|
|
228
|
+
if fids[fieldName]
|
|
229
|
+
clist << fids[fieldName]
|
|
230
|
+
clist << "." unless fieldName == "message"
|
|
231
|
+
else
|
|
232
|
+
raise "'#{fieldName}' fid entry is missing from the 'fids' Hash"
|
|
233
|
+
end
|
|
234
|
+
if not fieldNames[fieldName]
|
|
235
|
+
raise "'#{fieldName}' QuickBase field name entry is missing from the 'fieldNames' Hash"
|
|
236
|
+
end
|
|
237
|
+
}
|
|
238
|
+
qbc = QuickBase::Client.new(@username,@password)
|
|
239
|
+
if qbc and qbc.requestSucceeded
|
|
240
|
+
qbc.getSchema(dbid)
|
|
241
|
+
if qbc.requestSucceeded
|
|
242
|
+
emailMessages = qbc.getAllValuesForFields(dbid,fieldNames.values,query,qname,qid,clist)
|
|
243
|
+
emailMessages = nil if emailMessages.length == 0
|
|
244
|
+
else
|
|
245
|
+
raise "Error accessing QuickBase table '#{dbid}'."
|
|
246
|
+
end
|
|
247
|
+
else
|
|
248
|
+
raise "Error accessing QuickBase. Please check your internet connection, username and password"
|
|
249
|
+
end
|
|
250
|
+
qbc.signOut if qbc
|
|
251
|
+
emailMessages
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def sendEmailMessages(emailMessages, emailConfiguration)
|
|
255
|
+
if emailMessages and emailConfiguration
|
|
256
|
+
raise "'emailMessages' must be a Hash" if not emailMessages.is_a?(Hash)
|
|
257
|
+
raise "'emailConfiguration' must be a Hash" if not emailConfiguration.is_a?(Hash)
|
|
258
|
+
(0..(emailMessages["from"].length-1)).each{|i|
|
|
259
|
+
begin
|
|
260
|
+
|
|
261
|
+
from = emailMessages["from"][i]
|
|
262
|
+
toAddresses = emailMessages["to"][i].split(/\<BR\/\>/)
|
|
263
|
+
subject = emailMessages["subject"][i]
|
|
264
|
+
subject.gsub!("<BR/>","")
|
|
265
|
+
message = emailMessages["message"][i]
|
|
266
|
+
message.gsub!("<BR/>","")
|
|
267
|
+
|
|
268
|
+
sendEmail(from,
|
|
269
|
+
toAddresses,
|
|
270
|
+
subject,
|
|
271
|
+
message,
|
|
272
|
+
emailConfiguration["mailServer"][0],
|
|
273
|
+
emailConfiguration["mailPort"][0].to_i,
|
|
274
|
+
emailConfiguration["fromDomain"][0])
|
|
275
|
+
|
|
276
|
+
puts "Sent '#{subject}' email."
|
|
277
|
+
|
|
278
|
+
rescue StandardError => error
|
|
279
|
+
puts error
|
|
280
|
+
end
|
|
281
|
+
}
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def Emailer.sendEmailsFromQuickBase(username,password,configDBID,emailsDBID)
|
|
286
|
+
emailer = Emailer.new(username,password)
|
|
287
|
+
|
|
288
|
+
configFieldNames = Hash.new
|
|
289
|
+
configFieldNames["mailServer"]="mailServer"
|
|
290
|
+
configFieldNames["mailPort"]="mailPort"
|
|
291
|
+
configFieldNames["fromDomain"]="fromDomain"
|
|
292
|
+
configFieldNames["authenticationType"]="authenticationType"
|
|
293
|
+
|
|
294
|
+
configFieldIDs = Hash.new
|
|
295
|
+
configFieldIDs["mailServer"]="6"
|
|
296
|
+
configFieldIDs["mailPort"]="7"
|
|
297
|
+
configFieldIDs["fromDomain"]="8"
|
|
298
|
+
configFieldIDs["authenticationType"]="9"
|
|
299
|
+
|
|
300
|
+
emailConfiguration = emailer.readEmailServerConfigFromQuickBase(configDBID,configFieldNames,configFieldIDs)
|
|
301
|
+
if emailConfiguration
|
|
302
|
+
puts "Read an email configuration from '#{configDBID}'."
|
|
303
|
+
|
|
304
|
+
emailFieldNames = Hash.new
|
|
305
|
+
emailFieldNames["from"]="from"
|
|
306
|
+
emailFieldNames["to"]="to"
|
|
307
|
+
emailFieldNames["subject"]="subject"
|
|
308
|
+
emailFieldNames["message"]="message"
|
|
309
|
+
|
|
310
|
+
emailFieldIDs = Hash.new
|
|
311
|
+
emailFieldIDs["from"]="6"
|
|
312
|
+
emailFieldIDs["to"]="7"
|
|
313
|
+
emailFieldIDs["subject"]="8"
|
|
314
|
+
emailFieldIDs["message"]="9"
|
|
315
|
+
|
|
316
|
+
emailMessages = emailer.readEmailsToSendFromQuickBase(emailsDBID,emailFieldNames,emailFieldIDs)
|
|
317
|
+
if emailMessages and emailMessages["from"].length > 0
|
|
318
|
+
numMessages = emailMessages["from"].length
|
|
319
|
+
puts "Read #{numMessages} email messages from '#{emailsDBID}'."
|
|
320
|
+
emailer.sendEmailMessages(emailMessages, emailConfiguration)
|
|
321
|
+
else
|
|
322
|
+
puts "Did not read email messages from '#{emailsDBID}'."
|
|
323
|
+
end
|
|
324
|
+
else
|
|
325
|
+
puts "Did not read an email configuration from '#{configDBID}'."
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
end #class Emailer
|
|
330
|
+
|
|
331
|
+
end #module QuickBase
|
|
332
|
+
|
|
333
|
+
#QuickBase::Emailer.sendEmailsFromQuickBase(ARGV[0],ARGV[1],ARGV[2],ARGV[3]) if ARGV[3]
|
|
334
|
+
|
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
#--#####################################################################
|
|
2
|
+
# Copyright (c) 2009 Gareth Lewis and Intuit, Inc.
|
|
3
|
+
#
|
|
4
|
+
# All rights reserved. This program and the accompanying materials
|
|
5
|
+
# are made available under the terms of the Eclipse Public License v1.0
|
|
6
|
+
# which accompanies this distribution, and is available at
|
|
7
|
+
# http://www.opensource.org/licenses/eclipse-1.0.php
|
|
8
|
+
#
|
|
9
|
+
# Contributors:
|
|
10
|
+
# Gareth Lewis - Initial contribution.
|
|
11
|
+
# Intuit Partner Platform.
|
|
12
|
+
#++#####################################################################
|
|
13
|
+
|
|
14
|
+
require 'QuickBaseClient'
|
|
15
|
+
require 'tk'
|
|
16
|
+
|
|
17
|
+
module QuickBase
|
|
18
|
+
|
|
19
|
+
# This class maintains a list of QuickBase table changes to watch
|
|
20
|
+
# for, how to be notified of the changes, how frequently to check for the
|
|
21
|
+
# changes and when to start and stop checking. It can stop checking
|
|
22
|
+
# after a specific number of checks, or stop checking after a specific
|
|
23
|
+
# number of successful checks. It can also check for records changes
|
|
24
|
+
# that only meet certain conditions.
|
|
25
|
+
class EventNotifier
|
|
26
|
+
|
|
27
|
+
attr_reader :qbc
|
|
28
|
+
attr_writer :qbc
|
|
29
|
+
|
|
30
|
+
def initialize(qbc=nil, username=nil,password=nil)
|
|
31
|
+
@username,@password = username,password
|
|
32
|
+
if qbc
|
|
33
|
+
@qbc = qbc
|
|
34
|
+
elsif @username and @password
|
|
35
|
+
@qbc = QuickBase::Client.new(@username,@password)
|
|
36
|
+
end
|
|
37
|
+
@eventNotifications = Array.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# In addition to a basic record add/modify/delete events,
|
|
41
|
+
# check these conditions before sending a notification.
|
|
42
|
+
class RecordCondition
|
|
43
|
+
|
|
44
|
+
# fieldName: field to test, e.g. "Date"
|
|
45
|
+
# logicalOperator: kind of test, e.g. "OAF"
|
|
46
|
+
# fieldValue: value to test for, e.g. "12/31/2006"
|
|
47
|
+
def initialize(fieldName,logicalOperator,fieldValue)
|
|
48
|
+
@fieldName,@logicalOperator,@fieldValue=fieldName,logicalOperator,fieldValue
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate(qbc,dbid)
|
|
52
|
+
if qbc.lookupFieldIDByName(@fieldName)
|
|
53
|
+
fieldType = qbc.lookupFieldTypeByName(@fieldName)
|
|
54
|
+
if fieldType
|
|
55
|
+
queryOperator = qbc.verifyQueryOperator(@logicalOperator,fieldType)
|
|
56
|
+
if queryOperator.nil? or queryOperator.length == 0
|
|
57
|
+
raise "Invalid query operator '#{@logicalOperator}' for field '#{@fieldName}' in table #{dbid}"
|
|
58
|
+
else
|
|
59
|
+
if @fieldValue.length > 0 #any field can be blanked out
|
|
60
|
+
case fieldType
|
|
61
|
+
when "checkbox"
|
|
62
|
+
if !(@fieldValue == "1" or @fieldValue == "0")
|
|
63
|
+
raise "Invalid data '#{@fieldValue}' for checkbox field #{@fieldName}"
|
|
64
|
+
end
|
|
65
|
+
when "date"
|
|
66
|
+
fieldValue.gsub!("/","-")
|
|
67
|
+
if !fieldValue.match(/[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]/)
|
|
68
|
+
raise "Invalid data '#{@fieldValue}' for date field #{@fieldName}"
|
|
69
|
+
end
|
|
70
|
+
when "duration", "float", "currency", "rating"
|
|
71
|
+
fieldValue.gsub!(",","")
|
|
72
|
+
if !fieldValue.match(/[0-9]*\.?[0-9]*/)
|
|
73
|
+
raise "Invalid data '#{@fieldValue}' for field #{@fieldName}"
|
|
74
|
+
end
|
|
75
|
+
when "phone"
|
|
76
|
+
if !fieldValue.match(/[0-9|\.|x]+/)
|
|
77
|
+
raise "Invalid data '#{@fieldValue}' for phone field #{@fieldName}"
|
|
78
|
+
end
|
|
79
|
+
when "file"
|
|
80
|
+
if FileTest.exist?(fieldValue)
|
|
81
|
+
@fieldIsValidFileAttachment = true
|
|
82
|
+
else
|
|
83
|
+
raise "Invalid file name '#{@fieldValue}' for file attachment field #{@fieldName}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
raise "Unable to validate field type for field '#{@fieldName}' in table #{dbid}"
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
raise "Unable to validate field '#{@fieldName}' in table #{dbid}"
|
|
93
|
+
end
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# A record is added/modified/deleted from a table
|
|
100
|
+
class TableEvent
|
|
101
|
+
|
|
102
|
+
attr_reader :url
|
|
103
|
+
|
|
104
|
+
def initialize(table, eventType=["recordAdded","recordModified"],application=nil,recordCondition=nil)
|
|
105
|
+
if eventType.is_a?(String)
|
|
106
|
+
validateEventType(eventType)
|
|
107
|
+
elsif eventType.is_a?(Array)
|
|
108
|
+
eventType.each{|et| validateEventType(et) }
|
|
109
|
+
end
|
|
110
|
+
@table,@eventType,@application,@recordCondition=table,eventType,application,recordCondition
|
|
111
|
+
@lastModifiedTime = 0
|
|
112
|
+
@lastRecModTime = 0
|
|
113
|
+
@numRecords = 0
|
|
114
|
+
@url = "http://www.quickbase.com"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def validateEventType(eventType)
|
|
118
|
+
if @validEventTypes.nil?
|
|
119
|
+
@validEventTypes = Array.new
|
|
120
|
+
@validEventTypes << "recordAdded"
|
|
121
|
+
@validEventTypes << "recordModified"
|
|
122
|
+
@validEventTypes << "recordDeleted"
|
|
123
|
+
end
|
|
124
|
+
if ! (eventType.is_a?(String) and @validEventTypes.include?(eventType))
|
|
125
|
+
raise "Invalid event type '#{eventType}'."
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def isEventType?(type)
|
|
130
|
+
ret=false
|
|
131
|
+
if @eventType.is_a?(String)
|
|
132
|
+
ret = @eventType == type
|
|
133
|
+
elsif @eventType.is_a?(Array)
|
|
134
|
+
@eventType.each{|et|
|
|
135
|
+
if et == type
|
|
136
|
+
ret = true
|
|
137
|
+
break
|
|
138
|
+
end
|
|
139
|
+
}
|
|
140
|
+
end
|
|
141
|
+
ret
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def validateApplicationAndTable(qbc)
|
|
145
|
+
if @application and @application.length > 0
|
|
146
|
+
@table = @application if @table.nil?
|
|
147
|
+
qbc.findDBByName(@application)
|
|
148
|
+
if qbc.requestSucceeded
|
|
149
|
+
qbc.lookupChdbid(@table)
|
|
150
|
+
if qbc.requestSucceeded
|
|
151
|
+
@dbid = qbc.dbid.dup
|
|
152
|
+
qbc._getSchema
|
|
153
|
+
@tablename = @table.dup
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
elsif @table and @table.length > 0
|
|
157
|
+
qbc.getSchema(@table)
|
|
158
|
+
if qbc.requestSucceeded
|
|
159
|
+
@dbid = qbc.dbid.dup
|
|
160
|
+
@tableName = qbc.getResponseElement( "table/name" ).text
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
raise "table and/or application must be specified"
|
|
164
|
+
end
|
|
165
|
+
raise "Error retrieving schema for table #{@table}" if @dbid.nil?
|
|
166
|
+
@url = "https://www.quickbase.com/db/#{@dbid}"
|
|
167
|
+
@dbid
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def validateRecordCondition(qbc)
|
|
171
|
+
if @recordCondition
|
|
172
|
+
if @recordCondition.is_a?(RecordCondition)
|
|
173
|
+
@recordCondition.validate(qbc,@dbid)
|
|
174
|
+
elsif @recordCondition.is_a?(Array)
|
|
175
|
+
@recordCondition.each{|rc|
|
|
176
|
+
if rc.is_a?(RecordCondition)
|
|
177
|
+
rc.validate(qbc,@dbid)
|
|
178
|
+
else
|
|
179
|
+
raise "recordcondition must be a RecordCondition or Array of RecordConditions"
|
|
180
|
+
end
|
|
181
|
+
}
|
|
182
|
+
else
|
|
183
|
+
raise "recordcondition must be a RecordCondition or Array of RecordConditions"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
true
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def validate(qbc)
|
|
190
|
+
if validateApplicationAndTable(qbc)
|
|
191
|
+
validateRecordCondition(qbc)
|
|
192
|
+
end
|
|
193
|
+
true
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def eventOccurred?(qbc)
|
|
197
|
+
query = ""
|
|
198
|
+
ret = false
|
|
199
|
+
qbc.getDBInfo(@dbid)
|
|
200
|
+
if qbc.requestSucceeded
|
|
201
|
+
if @lastModifiedTime > 0 and qbc.lastModifiedTime.to_i > @lastModifiedTime
|
|
202
|
+
if isEventType?("recordModified")
|
|
203
|
+
if qbc.lastRecModTime.to_i > @lastRecModTime
|
|
204
|
+
query = "{'2'.GT.'#{@lastRecModTime}'}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
if isEventType?("recordAdded")
|
|
208
|
+
if qbc.numRecords.to_i > @numRecords
|
|
209
|
+
if query.length > 0
|
|
210
|
+
query << "OR{'1'.GT.'#{@lastModifiedTime}'}"
|
|
211
|
+
else
|
|
212
|
+
query = "{'1'.GT.'#{@lastModifiedTime}'}"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
if isEventType?("recordDeleted")
|
|
217
|
+
if qbc.numRecords.to_i < @numRecords
|
|
218
|
+
ret = true
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
#puts query
|
|
222
|
+
if query.length > 0
|
|
223
|
+
@records = qbc.getAllValuesForFields(@dbid, "Record ID#", query, nil, nil, "3")
|
|
224
|
+
puts "Found #{@records.length} records."
|
|
225
|
+
ret = @records.length > 0
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
@numRecords = qbc.numRecords.to_i
|
|
229
|
+
@lastModifiedTime = qbc.lastModifiedTime.to_i
|
|
230
|
+
@lastRecModTime = qbc.lastRecModTime.to_i
|
|
231
|
+
else
|
|
232
|
+
raise "Error getting information for table #{@dbid}"
|
|
233
|
+
end
|
|
234
|
+
ret
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def tableEventNotificationMessage
|
|
238
|
+
message = "Records have been "
|
|
239
|
+
if isEventType?("recordAdded")
|
|
240
|
+
eventTypeMessage = "added"
|
|
241
|
+
else
|
|
242
|
+
eventTypeMessage = ""
|
|
243
|
+
end
|
|
244
|
+
if isEventType?("recordModified")
|
|
245
|
+
if eventTypeMessage.length > 0
|
|
246
|
+
eventTypeMessage << " or modified"
|
|
247
|
+
else
|
|
248
|
+
eventTypeMessage = "modified"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
if isEventType?("recordDeleted")
|
|
252
|
+
if eventTypeMessage.length > 0
|
|
253
|
+
eventTypeMessage << "or deleted"
|
|
254
|
+
else
|
|
255
|
+
eventTypeMessage = "deleted"
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
message << eventTypeMessage
|
|
259
|
+
message << " in the '#{@tableName}' QuickBase table."
|
|
260
|
+
return @tableName,message
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Frequency and start/stop time of checking for TableEvent
|
|
266
|
+
class EventCheckPolicy
|
|
267
|
+
|
|
268
|
+
attr_reader :interval, :starttime, :stoptime, :nextCheckTime, :numChecks, :numSuccessfulChecks
|
|
269
|
+
|
|
270
|
+
# interval: minutes between checks
|
|
271
|
+
# starttime: start checking at this time
|
|
272
|
+
# stoptime: stop checking at this time
|
|
273
|
+
# numChecks: check this number of times then stop
|
|
274
|
+
# numSuccessfulChecks: stop checking after changes found this number of times
|
|
275
|
+
def initialize(interval=nil,starttime=nil,stoptime=nil,numChecks=-1,numSuccessfulChecks=-1 )
|
|
276
|
+
if interval
|
|
277
|
+
if interval.is_a?(Integer) and interval > 0
|
|
278
|
+
@interval = interval * 60
|
|
279
|
+
else
|
|
280
|
+
raise "interval is not a positive number"
|
|
281
|
+
end
|
|
282
|
+
else
|
|
283
|
+
@interval = 1 * 60
|
|
284
|
+
end
|
|
285
|
+
if starttime
|
|
286
|
+
if starttime.is_a?(Time)
|
|
287
|
+
@starttime = starttime
|
|
288
|
+
else
|
|
289
|
+
raise "starttime must be a Time object"
|
|
290
|
+
end
|
|
291
|
+
else
|
|
292
|
+
@starttime = Time.now
|
|
293
|
+
end
|
|
294
|
+
if stoptime and !stoptime.is_a?(Time)
|
|
295
|
+
raise "stoptime must be a Time object"
|
|
296
|
+
end
|
|
297
|
+
@stoptime = stoptime
|
|
298
|
+
if @starttime and @stoptime and @starttime > @stoptime
|
|
299
|
+
raise "starttime must be before stoptime"
|
|
300
|
+
end
|
|
301
|
+
@numSuccessfulChecks = numSuccessfulChecks
|
|
302
|
+
if @numSuccessfulChecks.nil?
|
|
303
|
+
@numSuccessfulChecks = -1
|
|
304
|
+
elsif !@numSuccessfulChecks.is_a?(Integer)
|
|
305
|
+
raise "numSuccessfulChecks must be a number"
|
|
306
|
+
end
|
|
307
|
+
if @numSuccessfulChecks > 0
|
|
308
|
+
@numChecks = -1
|
|
309
|
+
else
|
|
310
|
+
@numChecks = numChecks
|
|
311
|
+
if @numChecks.nil?
|
|
312
|
+
@numChecks = -1
|
|
313
|
+
elsif !@numChecks.is_a?(Integer)
|
|
314
|
+
raise "numChecks must be a number"
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
setNextCheckTime(true)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def setNextCheckTime(initializing=false,checkSucceeded=false)
|
|
321
|
+
if @nextCheckTime
|
|
322
|
+
@nextCheckTime += @interval
|
|
323
|
+
else
|
|
324
|
+
@nextCheckTime = @starttime + @interval
|
|
325
|
+
end
|
|
326
|
+
if checkSucceeded and @numSuccessfulChecks > 0
|
|
327
|
+
@numSuccessfulChecks = @numSuccessfulChecks - 1
|
|
328
|
+
end
|
|
329
|
+
@stopChecking = true if @numSuccessfulChecks == 0
|
|
330
|
+
if !initializing and @numChecks > 0
|
|
331
|
+
@numChecks = @numChecks - 1
|
|
332
|
+
@stopChecking = true if @numChecks == 0
|
|
333
|
+
end
|
|
334
|
+
if !@stopChecking
|
|
335
|
+
@stopChecking = (@stoptime and @nextCheckTime > @stoptime)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def stopChecking?
|
|
340
|
+
@stopChecking
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# What to do when a TableEvent has occurred
|
|
345
|
+
class Notification
|
|
346
|
+
|
|
347
|
+
attr_reader :beep, :message, :title, :launchBrowser
|
|
348
|
+
|
|
349
|
+
def initialize(beep=true,message="A QuickBase event has occurred.",title="QuickBase Event",launchBrowser=true)
|
|
350
|
+
if beep or message
|
|
351
|
+
@beep = beep
|
|
352
|
+
@message = message
|
|
353
|
+
@message = "" if @message.nil?
|
|
354
|
+
@title = title
|
|
355
|
+
@title = "" if @title.nil?
|
|
356
|
+
@launchBrowser = launchBrowser
|
|
357
|
+
else
|
|
358
|
+
raise "Notification must be a beep and/or a message"
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def overrideDefaultTitleAndMessage(title,withThis)
|
|
363
|
+
if @message == "A QuickBase event has occurred."
|
|
364
|
+
@message = withThis
|
|
365
|
+
@title = title
|
|
366
|
+
end
|
|
367
|
+
return @title,@message
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Event, notification, and checking policy
|
|
373
|
+
class EventNotification
|
|
374
|
+
|
|
375
|
+
attr_reader :notification
|
|
376
|
+
|
|
377
|
+
def initialize(tableEvent, notification=nil, eventCheckPolicy=nil)
|
|
378
|
+
if tableEvent and tableEvent.is_a?(TableEvent)
|
|
379
|
+
@tableEvent,@notification,@eventCheckPolicy=tableEvent,notification,eventCheckPolicy
|
|
380
|
+
@notification = Notification.new if @notification.nil?
|
|
381
|
+
@eventCheckPolicy = EventCheckPolicy.new if @eventCheckPolicy.nil?
|
|
382
|
+
else
|
|
383
|
+
raise "tableEvent must be TableEvent"
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def method_missing(method, *args)
|
|
388
|
+
if method == :nextCheckTime
|
|
389
|
+
@eventCheckPolicy.nextCheckTime
|
|
390
|
+
elsif method == :setNextCheckTime
|
|
391
|
+
@eventCheckPolicy.setNextCheckTime(args[0],args[1])
|
|
392
|
+
elsif method == :stopChecking?
|
|
393
|
+
@eventCheckPolicy.stopChecking?
|
|
394
|
+
elsif method == :numChecks
|
|
395
|
+
@eventCheckPolicy.numChecks
|
|
396
|
+
elsif method == :numSuccessfulChecks
|
|
397
|
+
@eventCheckPolicy.numSuccessfulChecks
|
|
398
|
+
elsif method == :eventOccurred?
|
|
399
|
+
@tableEvent.eventOccurred?(@qbc)
|
|
400
|
+
elsif method == :url
|
|
401
|
+
@tableEvent.url
|
|
402
|
+
elsif method == :tableEventNotificationMessage
|
|
403
|
+
@tableEvent.tableEventNotificationMessage
|
|
404
|
+
else
|
|
405
|
+
super.method_missing(method)
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def validate(qbc)
|
|
410
|
+
if qbc and qbc.is_a?(QuickBase::Client)
|
|
411
|
+
@tableEvent.validate(qbc)
|
|
412
|
+
@qbc = qbc
|
|
413
|
+
else
|
|
414
|
+
raise "qbc must be an instance of QuickBase::Client"
|
|
415
|
+
end
|
|
416
|
+
true
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
end # class EventNotification
|
|
420
|
+
|
|
421
|
+
def addEventNotification(eventNotification)
|
|
422
|
+
@eventNotifications << eventNotification if eventNotification.validate(@qbc)
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Call with a block to run code when an event occurs, or
|
|
426
|
+
# call without a block to show a message and/or beep.
|
|
427
|
+
# The loop stops if all event checks are beyond their stop time.
|
|
428
|
+
def startChecking
|
|
429
|
+
startTime = Time.now
|
|
430
|
+
firstLoop = true
|
|
431
|
+
loop {
|
|
432
|
+
timeNow = Time.now
|
|
433
|
+
stopChecking = true
|
|
434
|
+
@eventNotifications.each { |eventNotification|
|
|
435
|
+
unless eventNotification.stopChecking?
|
|
436
|
+
stopChecking = false
|
|
437
|
+
title,message=eventNotification.tableEventNotificationMessage
|
|
438
|
+
if timeNow > eventNotification.nextCheckTime
|
|
439
|
+
puts "Checking for changes in QuickBase. (#{title} - #{timeNow})"
|
|
440
|
+
checkSucceeded = false
|
|
441
|
+
if eventNotification.eventOccurred?
|
|
442
|
+
checkSucceeded = true
|
|
443
|
+
if block_given?
|
|
444
|
+
yield eventNotification
|
|
445
|
+
else
|
|
446
|
+
eventNotification.notification.overrideDefaultTitleAndMessage(title,message)
|
|
447
|
+
notify(eventNotification.notification,eventNotification.url)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
eventNotification.setNextCheckTime(false,checkSucceeded)
|
|
451
|
+
unless eventNotification.stopChecking?
|
|
452
|
+
checkTimeMessage = "Next check time for '#{title}' will be #{eventNotification.nextCheckTime}."
|
|
453
|
+
if eventNotification.numSuccessfulChecks > 0
|
|
454
|
+
checkTimeMessage << " (#{eventNotification.numSuccessfulChecks } more successful checks will be performed)."
|
|
455
|
+
elsif eventNotification.numChecks > 0
|
|
456
|
+
checkTimeMessage << " (#{eventNotification.numChecks} more checks will be performed)."
|
|
457
|
+
end
|
|
458
|
+
puts checkTimeMessage
|
|
459
|
+
end
|
|
460
|
+
elsif firstLoop
|
|
461
|
+
checkTimeMessage = "Next check time for '#{title}' will be #{eventNotification.nextCheckTime}."
|
|
462
|
+
puts checkTimeMessage
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
}
|
|
466
|
+
firstLoop = false
|
|
467
|
+
break if stopChecking
|
|
468
|
+
}
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# Event occurred: beep and/or show a message on separate thread
|
|
472
|
+
def notify(notification,url)
|
|
473
|
+
Tk.bell if notification.beep
|
|
474
|
+
if notification.message
|
|
475
|
+
showMessage(notification.title,notification.message,url,notification.launchBrowser)
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def showMessage(messageTitle,message,url,launchBrowser)
|
|
480
|
+
|
|
481
|
+
message = "#{message}\n\nLaunch QuickBase?" if url and launchBrowser
|
|
482
|
+
|
|
483
|
+
root = TkRoot.new{ title messageTitle }
|
|
484
|
+
frame = TkFrame.new(root){
|
|
485
|
+
pack "side" => "top"
|
|
486
|
+
borderwidth 8
|
|
487
|
+
}
|
|
488
|
+
messageLabel = TkLabel.new(frame){
|
|
489
|
+
text message
|
|
490
|
+
font "Arial 10 bold"
|
|
491
|
+
pack "side"=>"top"
|
|
492
|
+
}
|
|
493
|
+
if url and launchBrowser
|
|
494
|
+
buttonFrame = TkFrame.new(frame){
|
|
495
|
+
pack "side" => "bottom"
|
|
496
|
+
width 50
|
|
497
|
+
}
|
|
498
|
+
yesButton = TkButton.new(buttonFrame){
|
|
499
|
+
text "Yes"
|
|
500
|
+
font "Arial 10 bold"
|
|
501
|
+
pack "side"=>"left", "padx"=>5, "pady"=>5
|
|
502
|
+
}
|
|
503
|
+
yesButton.command {
|
|
504
|
+
launchURL(url)
|
|
505
|
+
Tk.exit
|
|
506
|
+
}
|
|
507
|
+
noButton = TkButton.new(buttonFrame){
|
|
508
|
+
text "No"
|
|
509
|
+
font "Arial 10 bold"
|
|
510
|
+
pack "side"=>"right","padx"=>5, "pady"=>5
|
|
511
|
+
}
|
|
512
|
+
noButton.command { Tk.exit }
|
|
513
|
+
else
|
|
514
|
+
okButton = TkButton.new(frame){
|
|
515
|
+
text "OK"
|
|
516
|
+
font "Arial 10 bold"
|
|
517
|
+
pack "side"=>"bottom"
|
|
518
|
+
}
|
|
519
|
+
okButton.command { Tk.exit }
|
|
520
|
+
end
|
|
521
|
+
Tk.mainloop
|
|
522
|
+
Tk.restart
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def launchURL(url)
|
|
526
|
+
url.gsub!("&","^&")
|
|
527
|
+
url = "start #{url}" if RUBY_PLATFORM.split("-")[1].include?("mswin")
|
|
528
|
+
if !system(url)
|
|
529
|
+
message = "Error launching browser at #{url}."
|
|
530
|
+
Tk.messageBox({"icon"=>"error","title"=>"QuickBase Event Notifier", "message" => message})
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
# Simple method to get messages when records are added or modified in a table
|
|
535
|
+
def EventNotifier.watch(username,password,dbid)
|
|
536
|
+
en = EventNotifier.new(nil,username,password)
|
|
537
|
+
notification = EventNotification.new(TableEvent.new(dbid))
|
|
538
|
+
en.addEventNotification(notification)
|
|
539
|
+
en.startChecking
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Simple method to run code when records are added or modified in a table
|
|
543
|
+
def EventNotifier.watchAndRunCode(username,password,dbid)
|
|
544
|
+
en = EventNotifier.new(nil,username,password)
|
|
545
|
+
notification = EventNotification.new(TableEvent.new(dbid))
|
|
546
|
+
en.addEventNotification(notification)
|
|
547
|
+
en.startChecking { |eventNotification|
|
|
548
|
+
yield eventNotification
|
|
549
|
+
}
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# Simple method to check one time only, 15 minutes from now, then stop
|
|
553
|
+
def EventNotifier.checkOnce(username,password,dbid)
|
|
554
|
+
en = EventNotifier.new(nil,username,password)
|
|
555
|
+
notification = EventNotification.new(TableEvent.new(dbid),nil,EventCheckPolicy.new(15,nil,nil,1))
|
|
556
|
+
en.addEventNotification(notification)
|
|
557
|
+
en.startChecking
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
# Simple method to wait for one QuickBase change then stop
|
|
561
|
+
def EventNotifier.waitOnce(username,password,dbid)
|
|
562
|
+
en = EventNotifier.new(nil,username,password)
|
|
563
|
+
notification = EventNotification.new(TableEvent.new(dbid),nil,EventCheckPolicy.new(nil,nil,nil,nil,1))
|
|
564
|
+
en.addEventNotification(notification)
|
|
565
|
+
en.startChecking
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# Simple method to check for records created after 2006 and modified today
|
|
569
|
+
def EventNotifier.watch2007Changes(username,password,dbid)
|
|
570
|
+
en = EventNotifier.new(nil,username,password)
|
|
571
|
+
rc = RecordCondition.new("Date Created", "OAF", "01-01-2007")
|
|
572
|
+
te = TableEvent.new(dbid,"recordModified",nil,rc)
|
|
573
|
+
notification = EventNotification.new(te)
|
|
574
|
+
en.addEventNotification(notification)
|
|
575
|
+
en.startChecking
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
end #class EventNotifier
|
|
579
|
+
|
|
580
|
+
end #module QuickBase
|
|
581
|
+
|
|
582
|
+
#if ARGV[2] and ARGV[2] == "watchCommunityForum"
|
|
583
|
+
# QuickBase::EventNotifier.watch(ARGV[0],ARGV[1],"bbqm84dzy")
|
|
584
|
+
#elsif ARGV[2] == "watchDBID" and ARGV[3] and ARGV[3].length > 0
|
|
585
|
+
# QuickBase::EventNotifier.watch(ARGV[0],ARGV[1],ARGV[3])
|
|
586
|
+
#elsif ARGV[2] == "checkDBIDOnce" and ARGV[3] and ARGV[3].length > 0
|
|
587
|
+
# QuickBase::EventNotifier.checkOnce(ARGV[0],ARGV[1],ARGV[3])
|
|
588
|
+
#elsif ARGV[2] == "waitDBIDOnce" and ARGV[3] and ARGV[3].length > 0
|
|
589
|
+
# QuickBase::EventNotifier.waitOnce(ARGV[0],ARGV[1],ARGV[3])
|
|
590
|
+
#elsif ARGV[2] == "watch2007Changes" and ARGV[3] and ARGV[3].length > 0
|
|
591
|
+
# QuickBase::EventNotifier.watch2007Changes(ARGV[0],ARGV[1],ARGV[3])
|
|
592
|
+
#end
|