# goats-requery.tcl
#
# This whole thing is derived from code I wrote as part of the
# Ars Digita Community System training problems sets,
# (see http://www.arsdigita.com/boot-camp/psets/3.xpset )
# specifically, the assignment to compare prices on a given book
# from several on-line book stores. Consequently, this code is
# ultimately derived from code distributed with the ACS, so proper
# attribution is due.
set exception_count 0
# parse form input
set_the_usual_form_variables
#sanity checks. "Graphable" flags this update for inclusion in
# the hourly charts. It may only be set by requests comming from
# the server itself. This is done by a cron job. Having a graphable
# flag is more robust (in my opinion) than selecting by the exact
# time, since it will still work if the automatic update runs a
# early or late.
if { ![info exists graphable] || [empty_string_p $graphable] } {
incr exception_count
append exception_text "No value for graphable."
}
if { ![regexp -nocase {^y$} $graphable] && ![regexp -nocase {^n$} $graphable]} {
incr exception_count
append exception_text "Invalid graphable $graphable."
}
if {[regexp -nocase {^y$} $graphable] && [string compare "137.22.99.75" [ns_conn peeraddr]] != 0} {
incr exception_count
append exception_text "Your IP address is not authorized to set graphable. "
}
set graphable [string toupper $graphable]
if { $exception_count > 0 } {
ad_return_complaint $exception_count $exception_text
# terminate execution of this thread (a goto!)
return
}
# return the URL of the goats page. Why use a function instead of a variable? Because
# it made sense as part of a more complex ADT which was used in the code I wrote for
# something else and subsequently modified to do this.
proc amzn_goats_url {} {
return "http://www.amazon.com/paypage/PC87DYXDCIC29"
# return "http://s1.amazon.com/exec/varzea/pay/T1IPCIVVU7AQLY"
}
# extract total money from Amazon web page
proc amzn_parse_money { page } {
if {[regexp {Total Collected:.*?\$(.*?)} $page match price]} {
return $price
} else {
ns_returnerror 408 "Unidentified error parsing amazon.com for total collected. Here is is, may you parse better:
$page"
}
}
# extract number of payments from Amazon web page
proc amzn_parse_payments { page } {
if {[regexp {of Payments:.*?size=-1>(.*?)} $page match price]} {
return $price
} else {
ns_returnerror 408 "Unidentified error parsing amazon.com for number of payments. Here is is, may you parse better:
$page"
}
}
# Another curiosity which is reveals the heritage of this code. Don't worry about it.
# it's part of a larger system designed to make the addition of other operations and
# bookstores easy and transparent to client code.
proc amzn_func {operation parameter} {
switch $operation {
"url" {
return [amzn_goats_url]
}
"url_body" {
if [catch {set temp [ns_httpget $parameter 3]}] {
ns_returnerror 408 "unable to contact amazon.com server with url $parameter"
} else {
return $temp
}
}
"body_money" {
return [amzn_parse_money $parameter]
}
"body_payments" {
return [amzn_parse_payments $parameter]
}
default {
ns_returnerror 500 "Invalid operator $operation on bookstore amzn"
return
}
}
}
# The only proc you need to know: an ADT defining basic server-specific ops for all servers so far
proc bookstore { storename operation parameter } {
switch $storename {
"amzn" {
return [amzn_func $operation $parameter]
}
default {
ns_returnerror 500 "Invalid call to bookstore. Storename $storename not recognized"
return
}
}
}
# set_the_usual_form_variables
# See, here's where the wierdness comes in. If you used all the funky code
# above to define a new bookstore, you could use this code right here
# with no changes other than replacing the parameter "amzn" in the [bookstore ]
# command. And you could put several bookstores in a list, and loop through
# them automatically.
set amzn_url1 [bookstore amzn url bob]
set amzn_body1 [bookstore amzn url_body $amzn_url1]
set amzn_money [bookstore amzn body_money $amzn_body1]
set amzn_payments [bookstore amzn body_payments $amzn_body1]
# More exception handling
if { ![info exists amzn_money] || [empty_string_p $amzn_money] } {
incr exception_count
append exception_text "No Money!"
}
if { ![info exists amzn_payments] || [empty_string_p $amzn_payments] } {
incr exception_count
append exception_text "No Payments!"
}
if { $exception_count > 0 } {
ad_return_complaint $exception_count $exception_text
# terminate execution of this thread (a goto!)
return
}
# This all just takes a comma out the value, so that 1,234 dolllars gets
# turned into 1 and 234, and then in 1234, so that the comma doesn't
# confuse Oracle.
regexp {([0-9]*),(.*$)} $amzn_money match big little
set amzn_money "$big$little"
# figure out what page this was called from so we can
# bounce the user right back there. That way, this same
# page can be linked to by both the daily and hourly
# graph pages, and the user will be sent back to the
# (now updated) page s/he was at before. Which is what
# we want.
set referer [ns_set get [ns_conn headers] Referer]
# This is debugging material it never gets actually returned.
set page_content "
test!
$referer
Query shows \$$amzn_money in $amzn_payments payments."
# Here's the beef. Insert the parsed values into Oracle
db_transaction {
db_dml lock_table "lock table goats_money in exclusive mode"
set new_key [db_string max_check_id "select nvl(max(check_id)+1,1)
from goats_money"]
db_dml insert_quote "insert into goats_money
(check_id, insertion_date, total_money, num_payments,graphable)
values
(:new_key, sysdate, :amzn_money , :amzn_payments, :graphable)
"
}
db_release_unused_handles
#ns_return 200 text/html $page_content
#ns_returnredirect goats-2.tcl
# And send the user back to whence they came
ns_returnredirect $referer