This is how to insert new soil moisture measurements data into the database. Click “Code” at the top of this page to download an rmarkdown version of this file!

First, load the necessary libraries

library(RPostgreSQL)
library(readr)
library(dplyr)
library(tidyr)
library(uuid)
library(magrittr)
library(kableExtra)
library(DT)

Load up the rodm2 package!

library(devtools)
install_github("khondula/rodm2")
library(rodm2)

Then connect to the database

password <- scan(".pgpass", what="")

db <- dbConnect(PostgreSQL(), 
                host = "sesync-postgis01.research.sesync.org",
                dbname = "choptank", 
                user = "palmergroup",
                password = password)

Upload and prepare data

Upload the spreadsheet with your new soil moisture data. Either use a file browser:

new_measurements <- file.choose() %>% read_csv()

Or supply the file path:

new_measurements <- read_csv("data/Field_measurements - SOIL_field_measurements.csv")

The new_measurements data frame should look something like this:

new_measurements %>% head() %>% kable(format = "markdown")

Data column names

Make sure the column names for data match the template. This chunk should return TRUE.

all(c("VWC_percent", "soil_EC_mscm", "soil_temp_C") %in% names(new_measurements))

Site codes

Make a new column with site code names called samplingfeaturecode if it isn’t there already. If you have the wetland site code and the transect point in 2 separate columns, you can combine them using paste.

new_measurements$samplingfeaturecode <- paste(new_measurements$Site, new_measurements$Location)
new_measurements$samplingfeaturecode <- gsub("SC ", "SC-", new_measurements$samplingfeaturecode)

Make sure all the sites in the new data are in the database by reading in the samplingfeatures table and checking all the site code names in the new data. The function check_samplingfeaturecodes (defined in helpers.R) returns the names of any sites not in the database compared to a given vector.

samplingfeatures <- dbGetQuery(db, "SELECT samplingfeaturecode FROM odm2.samplingfeatures")
check_samplingfeaturecodes(new_codes = new_measurements$samplingfeaturecode) %>% head()

If you need help figuring out the exact site names, look for site names in the database with a given pattern using the function get_site_names_like()

get_site_names_like("QB SC")

Fix site code names if needed. For example, soil chamber sites might need hyphens added.

new_measurements$samplingfeaturecode <- gsub(pattern = "SC ", "SC-", new_measurements$samplingfeaturecode)

Check site names again if needed. If there are no rows returned then all the site names match sampling feature codes in the database.

check_samplingfeaturecodes(new_measurements$samplingfeaturecode)

Method code

There needs to be a defined method in the methods table about how samples were collected. Make sure that sample collection method exists by checking the methods table online or reading it directly from the database using the dbReadTable() function. Note the methodcode or make a new one with the db_add_method() function.

dbReadTable(db, c("odm2", "methods")) %>% kable()
# db_add_method(methodname = "Soil moisture measurement",
#               methodcode = "soilmoistureTDR",
#               methodtypecv = "Instrument deployment",
#               methoddescription = "Point measurement of soil moisture using TDR probe with 1.5 inch probes")

Save the methodcode as a variable

methodcode = "soilmoistureTDR"

Date time formatting

Actions need a correctly formatted date and time. Format the date and time. If no time is given, assume midnight (to know that it isn’t a specific time). Also define the UTC offset variable (assuming it is the same as your computer’s timezone). Use ?strptime to find the correct character string for the input data or refer to strtime.org

new_measurements$datetime <- strptime(
  paste(new_measurements$Date, new_measurements$Time),
  format = "%B %d, %Y %H:%M:%S", tz = "")

Define UTC offset as a variable. This will be either -4 or -5 depending on whether its daylight savings time or not.

utcoffset <- format(Sys.time(), "%z") %>% substr(1, 3) %>% as.integer()

Annotations

Coming soon!

Add an annotation to an action or result

# dbReadTable(db, c("odm2", "cv_annotationtype"))
# db_add_annotation(annotationtypecv = "Action annotation",
#                   annotationtext = "measured less than 24 hours after rain")

More metadata

Specify some necessary metadata parameters. You will likely not need to change these. Kelly should probably add these as default function arguments so its not cluttering up this document as much.

actiontypecv = "Instrument deployment"
resulttypecv = "Measurement"
censorcodecv = "Not censored"
qualitycodecv = "Unknown"
aggregationstatisticcv = "Sporadic"
timeaggregationinterval = 1 # assume 1 minute
timeaggregationintervalunitsid = dbGetQuery(db, "SELECT unitsid FROM odm2.units WHERE unitsname = 'Minute'") # minute
processlinglevelid = 1 # definition = "Raw data" # processinglevelcode = 0
valuecount = 1
sampledmediumcv = "Soil"

Insert measurements data

This is where the magic happens! The function db_insert_measurements_sm() writes and runs SQL code to upload data from one row of the new_measurements data frame at a time. So db_insert_measurements_sm(1) would insert data from the first row. Use an apply statement to upload all of the data at once. The eval=FALSE argument means that this code will not run if you knit the whole document.

# db_insert_measurements_sm(1) # for just one measurement

sapply(1:nrow(new_measurements),
       function(x) db_insert_measurements_sm(x))

Query database

Use this chunk to make a data frame called sm_db with all the soil data in the database

get_sm_data <- function(){
  
  sql <- paste0("SELECT mrv.datavalue, mrv.valuedatetime, sf.samplingfeaturecode, r.featureactionid, v.variablecode, u.unitsname
 FROM odm2.measurementresultvalues mrv, odm2.results r, odm2.variables v, odm2.units u, odm2.samplingfeatures sf, odm2.featureactions fa
 WHERE r.variableid = v.variableid 
 AND r.featureactionid = fa.featureactionid
 AND fa.samplingfeatureid = sf.samplingfeatureid
 AND r.unitsid = u.unitsid
 AND mrv.resultid = r.resultid 
 AND r.sampledmediumcv = 'Soil'")

  sql <- gsub("\n", "", sql)
  dbGetQuery(db, sql)
}

sm_db <- get_sm_data()

organize back to the original format using the spread function in the tidyr package

sm_db_spread <- sm_db %>% group_by(samplingfeaturecode) %>%
  dplyr::select(-unitsname) %>%
  spread(variablecode, datavalue)
sm_db_spread %>% datatable()
LS0tCnRpdGxlOiAiSW5zZXJ0IHNvaWwgbW9pc3R1cmUgbWVhc3VyZW1lbnRzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGRmX3ByaW50OiBwYWdlZAplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKVGhpcyBpcyBob3cgdG8gaW5zZXJ0IG5ldyBzb2lsIG1vaXN0dXJlIG1lYXN1cmVtZW50cyBkYXRhIGludG8gdGhlIGRhdGFiYXNlLiBDbGljayAiQ29kZSIgYXQgdGhlIHRvcCBvZiB0aGlzIHBhZ2UgdG8gZG93bmxvYWQgYW4gcm1hcmtkb3duIHZlcnNpb24gb2YgdGhpcyBmaWxlIQoKRmlyc3QsIGxvYWQgdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KFJQb3N0Z3JlU1FMKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHV1aWQpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShEVCkKYGBgCgpMb2FkIHVwIHRoZSByb2RtMiBwYWNrYWdlIQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZGV2dG9vbHMpCmluc3RhbGxfZ2l0aHViKCJraG9uZHVsYS9yb2RtMiIpCmxpYnJhcnkocm9kbTIpCmBgYAoKVGhlbiBjb25uZWN0IHRvIHRoZSBkYXRhYmFzZQoKYGBge3J9CnBhc3N3b3JkIDwtIHNjYW4oIi5wZ3Bhc3MiLCB3aGF0PSIiKQoKZGIgPC0gZGJDb25uZWN0KFBvc3RncmVTUUwoKSwgCiAgICAgICAgICAgICAgICBob3N0ID0gInNlc3luYy1wb3N0Z2lzMDEucmVzZWFyY2guc2VzeW5jLm9yZyIsCiAgICAgICAgICAgICAgICBkYm5hbWUgPSAiY2hvcHRhbmsiLCAKICAgICAgICAgICAgICAgIHVzZXIgPSAicGFsbWVyZ3JvdXAiLAogICAgICAgICAgICAgICAgcGFzc3dvcmQgPSBwYXNzd29yZCkKYGBgCgoKIyBVcGxvYWQgYW5kIHByZXBhcmUgZGF0YQoKVXBsb2FkIHRoZSBzcHJlYWRzaGVldCB3aXRoIHlvdXIgbmV3IHNvaWwgbW9pc3R1cmUgZGF0YS4gRWl0aGVyIHVzZSBhIGZpbGUgYnJvd3NlcjoKCmBgYHtyLCBldmFsID0gRkFMU0V9Cm5ld19tZWFzdXJlbWVudHMgPC0gZmlsZS5jaG9vc2UoKSAlPiUgcmVhZF9jc3YoKQpgYGAKCk9yIHN1cHBseSB0aGUgZmlsZSBwYXRoOiAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpuZXdfbWVhc3VyZW1lbnRzIDwtIHJlYWRfY3N2KCJkYXRhL0ZpZWxkX21lYXN1cmVtZW50cyAtIFNPSUxfZmllbGRfbWVhc3VyZW1lbnRzLmNzdiIpCmBgYAoKVGhlIGBuZXdfbWVhc3VyZW1lbnRzYCBkYXRhIGZyYW1lIHNob3VsZCBsb29rIHNvbWV0aGluZyBsaWtlIHRoaXM6IAoKYGBge3J9Cm5ld19tZWFzdXJlbWVudHMgJT4lIGhlYWQoKSAlPiUga2FibGUoZm9ybWF0ID0gIm1hcmtkb3duIikKYGBgCgojIyBEYXRhIGNvbHVtbiBuYW1lcwoKTWFrZSBzdXJlIHRoZSBjb2x1bW4gbmFtZXMgZm9yIGRhdGEgbWF0Y2ggdGhlIHRlbXBsYXRlLiBUaGlzIGNodW5rIHNob3VsZCByZXR1cm4gYFRSVUVgLiAKCmBgYHtyfQphbGwoYygiVldDX3BlcmNlbnQiLCAic29pbF9FQ19tc2NtIiwgInNvaWxfdGVtcF9DIikgJWluJSBuYW1lcyhuZXdfbWVhc3VyZW1lbnRzKSkKYGBgCgojIyBTaXRlIGNvZGVzIAoKTWFrZSBhIG5ldyBjb2x1bW4gd2l0aCBzaXRlIGNvZGUgbmFtZXMgY2FsbGVkIHNhbXBsaW5nZmVhdHVyZWNvZGUgaWYgaXQgaXNuJ3QgdGhlcmUgYWxyZWFkeS4gSWYgeW91IGhhdmUgdGhlIHdldGxhbmQgc2l0ZSBjb2RlIGFuZCB0aGUgdHJhbnNlY3QgcG9pbnQgaW4gMiBzZXBhcmF0ZSBjb2x1bW5zLCB5b3UgY2FuIGNvbWJpbmUgdGhlbSB1c2luZyBwYXN0ZS4gCgpgYGB7cn0KbmV3X21lYXN1cmVtZW50cyRzYW1wbGluZ2ZlYXR1cmVjb2RlIDwtIHBhc3RlKG5ld19tZWFzdXJlbWVudHMkU2l0ZSwgbmV3X21lYXN1cmVtZW50cyRMb2NhdGlvbikKbmV3X21lYXN1cmVtZW50cyRzYW1wbGluZ2ZlYXR1cmVjb2RlIDwtIGdzdWIoIlNDICIsICJTQy0iLCBuZXdfbWVhc3VyZW1lbnRzJHNhbXBsaW5nZmVhdHVyZWNvZGUpCmBgYAoKTWFrZSBzdXJlIGFsbCB0aGUgc2l0ZXMgaW4gdGhlIG5ldyBkYXRhIGFyZSBpbiB0aGUgZGF0YWJhc2UgYnkgcmVhZGluZyBpbiB0aGUgc2FtcGxpbmdmZWF0dXJlcyB0YWJsZSBhbmQgY2hlY2tpbmcgYWxsIHRoZSBzaXRlIGNvZGUgbmFtZXMgaW4gdGhlIG5ldyBkYXRhLiBUaGUgZnVuY3Rpb24gY2hlY2tfc2FtcGxpbmdmZWF0dXJlY29kZXMgKGRlZmluZWQgaW4gYGhlbHBlcnMuUmApIHJldHVybnMgdGhlIG5hbWVzIG9mIGFueSBzaXRlcyBub3QgaW4gdGhlIGRhdGFiYXNlIGNvbXBhcmVkIHRvIGEgZ2l2ZW4gdmVjdG9yLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnNhbXBsaW5nZmVhdHVyZXMgPC0gZGJHZXRRdWVyeShkYiwgIlNFTEVDVCBzYW1wbGluZ2ZlYXR1cmVjb2RlIEZST00gb2RtMi5zYW1wbGluZ2ZlYXR1cmVzIikKY2hlY2tfc2FtcGxpbmdmZWF0dXJlY29kZXMobmV3X2NvZGVzID0gbmV3X21lYXN1cmVtZW50cyRzYW1wbGluZ2ZlYXR1cmVjb2RlKSAlPiUgaGVhZCgpCmBgYAoKSWYgeW91IG5lZWQgaGVscCBmaWd1cmluZyBvdXQgdGhlIGV4YWN0IHNpdGUgbmFtZXMsIGxvb2sgZm9yIHNpdGUgbmFtZXMgaW4gdGhlIGRhdGFiYXNlIHdpdGggYSBnaXZlbiBwYXR0ZXJuIHVzaW5nIHRoZSBmdW5jdGlvbiBgZ2V0X3NpdGVfbmFtZXNfbGlrZSgpYAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmdldF9zaXRlX25hbWVzX2xpa2UoIlFCIFNDIikKYGBgCgpGaXggc2l0ZSBjb2RlIG5hbWVzIGlmIG5lZWRlZC4gRm9yIGV4YW1wbGUsIHNvaWwgY2hhbWJlciBzaXRlcyBtaWdodCBuZWVkIGh5cGhlbnMgYWRkZWQuIAoKYGBge3J9Cm5ld19tZWFzdXJlbWVudHMkc2FtcGxpbmdmZWF0dXJlY29kZSA8LSBnc3ViKHBhdHRlcm4gPSAiU0MgIiwgIlNDLSIsIG5ld19tZWFzdXJlbWVudHMkc2FtcGxpbmdmZWF0dXJlY29kZSkKYGBgCgpDaGVjayBzaXRlIG5hbWVzIGFnYWluIGlmIG5lZWRlZC4gSWYgdGhlcmUgYXJlIG5vIHJvd3MgcmV0dXJuZWQgdGhlbiBhbGwgdGhlIHNpdGUgbmFtZXMgbWF0Y2ggc2FtcGxpbmcgZmVhdHVyZSBjb2RlcyBpbiB0aGUgZGF0YWJhc2UuIAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmNoZWNrX3NhbXBsaW5nZmVhdHVyZWNvZGVzKG5ld19tZWFzdXJlbWVudHMkc2FtcGxpbmdmZWF0dXJlY29kZSkKYGBgCgojIyBNZXRob2QgY29kZSAKClRoZXJlIG5lZWRzIHRvIGJlIGEgZGVmaW5lZCBtZXRob2QgaW4gdGhlIG1ldGhvZHMgdGFibGUgYWJvdXQgaG93IHNhbXBsZXMgd2VyZSBjb2xsZWN0ZWQuIE1ha2Ugc3VyZSB0aGF0IHNhbXBsZSBjb2xsZWN0aW9uIG1ldGhvZCBleGlzdHMgYnkgY2hlY2tpbmcgdGhlIFttZXRob2RzIHRhYmxlIG9ubGluZV0oaHR0cHM6Ly9wYWxtZXJsYWIudW1kLmVkdS9jaG9wdGFuay1kYi9tZXRob2RzLmh0bWwpIG9yIHJlYWRpbmcgaXQgZGlyZWN0bHkgZnJvbSB0aGUgZGF0YWJhc2UgdXNpbmcgdGhlIGBkYlJlYWRUYWJsZSgpYCBmdW5jdGlvbi4gTm90ZSB0aGUgKiptZXRob2Rjb2RlKiogb3IgbWFrZSBhIG5ldyBvbmUgd2l0aCB0aGUgYGRiX2FkZF9tZXRob2QoKWAgZnVuY3Rpb24uIAoKYGBge3J9CmRiUmVhZFRhYmxlKGRiLCBjKCJvZG0yIiwgIm1ldGhvZHMiKSkgJT4lIGthYmxlKCkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBkYl9hZGRfbWV0aG9kKG1ldGhvZG5hbWUgPSAiU29pbCBtb2lzdHVyZSBtZWFzdXJlbWVudCIsCiMgICAgICAgICAgICAgICBtZXRob2Rjb2RlID0gInNvaWxtb2lzdHVyZVREUiIsCiMgICAgICAgICAgICAgICBtZXRob2R0eXBlY3YgPSAiSW5zdHJ1bWVudCBkZXBsb3ltZW50IiwKIyAgICAgICAgICAgICAgIG1ldGhvZGRlc2NyaXB0aW9uID0gIlBvaW50IG1lYXN1cmVtZW50IG9mIHNvaWwgbW9pc3R1cmUgdXNpbmcgVERSIHByb2JlIHdpdGggMS41IGluY2ggcHJvYmVzIikKYGBgCgpTYXZlIHRoZSBtZXRob2Rjb2RlIGFzIGEgdmFyaWFibGUKCmBgYHtyfQptZXRob2Rjb2RlID0gInNvaWxtb2lzdHVyZVREUiIKYGBgCgojIyBEYXRlIHRpbWUgZm9ybWF0dGluZwoKQWN0aW9ucyBuZWVkIGEgY29ycmVjdGx5IGZvcm1hdHRlZCBkYXRlIGFuZCB0aW1lLiBGb3JtYXQgdGhlIGRhdGUgYW5kIHRpbWUuIElmIG5vIHRpbWUgaXMgZ2l2ZW4sIGFzc3VtZSBtaWRuaWdodCAodG8ga25vdyB0aGF0IGl0IGlzbid0IGEgc3BlY2lmaWMgdGltZSkuIEFsc28gZGVmaW5lIHRoZSBVVEMgb2Zmc2V0IHZhcmlhYmxlIChhc3N1bWluZyBpdCBpcyB0aGUgc2FtZSBhcyB5b3VyIGNvbXB1dGVyJ3MgdGltZXpvbmUpLiBVc2UgYD9zdHJwdGltZWAgdG8gZmluZCB0aGUgY29ycmVjdCBjaGFyYWN0ZXIgc3RyaW5nIGZvciB0aGUgaW5wdXQgZGF0YSBvciByZWZlciB0byBbc3RydGltZS5vcmddKGh0dHA6Ly9zdHJmdGltZS5vcmcvKQoKYGBge3J9Cm5ld19tZWFzdXJlbWVudHMkZGF0ZXRpbWUgPC0gc3RycHRpbWUoCiAgcGFzdGUobmV3X21lYXN1cmVtZW50cyREYXRlLCBuZXdfbWVhc3VyZW1lbnRzJFRpbWUpLAogIGZvcm1hdCA9ICIlQiAlZCwgJVkgJUg6JU06JVMiLCB0eiA9ICIiKQpgYGAKCkRlZmluZSBVVEMgb2Zmc2V0IGFzIGEgdmFyaWFibGUuIFRoaXMgd2lsbCBiZSBlaXRoZXIgLTQgb3IgLTUgZGVwZW5kaW5nIG9uIHdoZXRoZXIgaXRzIGRheWxpZ2h0IHNhdmluZ3MgdGltZSBvciBub3QuCgpgYGB7cn0KdXRjb2Zmc2V0IDwtIGZvcm1hdChTeXMudGltZSgpLCAiJXoiKSAlPiUgc3Vic3RyKDEsIDMpICU+JSBhcy5pbnRlZ2VyKCkKYGBgCgojIyBBbm5vdGF0aW9ucyAKCipDb21pbmcgc29vbiEqIAoKQWRkIGFuIGFubm90YXRpb24gdG8gYW4gYWN0aW9uIG9yIHJlc3VsdAoKYGBge3IsIGV2YWw9RkFMU0V9CiMgZGJSZWFkVGFibGUoZGIsIGMoIm9kbTIiLCAiY3ZfYW5ub3RhdGlvbnR5cGUiKSkKIyBkYl9hZGRfYW5ub3RhdGlvbihhbm5vdGF0aW9udHlwZWN2ID0gIkFjdGlvbiBhbm5vdGF0aW9uIiwKIyAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9udGV4dCA9ICJtZWFzdXJlZCBsZXNzIHRoYW4gMjQgaG91cnMgYWZ0ZXIgcmFpbiIpCmBgYAoKIyMgTW9yZSBtZXRhZGF0YSAKClNwZWNpZnkgc29tZSBuZWNlc3NhcnkgbWV0YWRhdGEgcGFyYW1ldGVycy4gWW91IHdpbGwgbGlrZWx5IG5vdCBuZWVkIHRvIGNoYW5nZSB0aGVzZS4gS2VsbHkgc2hvdWxkIHByb2JhYmx5IGFkZCB0aGVzZSBhcyBkZWZhdWx0IGZ1bmN0aW9uIGFyZ3VtZW50cyBzbyBpdHMgbm90IGNsdXR0ZXJpbmcgdXAgdGhpcyBkb2N1bWVudCBhcyBtdWNoLiAKCmBgYHtyfQphY3Rpb250eXBlY3YgPSAiSW5zdHJ1bWVudCBkZXBsb3ltZW50IgpyZXN1bHR0eXBlY3YgPSAiTWVhc3VyZW1lbnQiCmNlbnNvcmNvZGVjdiA9ICJOb3QgY2Vuc29yZWQiCnF1YWxpdHljb2RlY3YgPSAiVW5rbm93biIKYWdncmVnYXRpb25zdGF0aXN0aWNjdiA9ICJTcG9yYWRpYyIKdGltZWFnZ3JlZ2F0aW9uaW50ZXJ2YWwgPSAxICMgYXNzdW1lIDEgbWludXRlCnRpbWVhZ2dyZWdhdGlvbmludGVydmFsdW5pdHNpZCA9IGRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgdW5pdHNpZCBGUk9NIG9kbTIudW5pdHMgV0hFUkUgdW5pdHNuYW1lID0gJ01pbnV0ZSciKSAjIG1pbnV0ZQpwcm9jZXNzbGluZ2xldmVsaWQgPSAxICMgZGVmaW5pdGlvbiA9ICJSYXcgZGF0YSIgIyBwcm9jZXNzaW5nbGV2ZWxjb2RlID0gMAp2YWx1ZWNvdW50ID0gMQpzYW1wbGVkbWVkaXVtY3YgPSAiU29pbCIKYGBgCgojIEluc2VydCBtZWFzdXJlbWVudHMgZGF0YQoKVGhpcyBpcyB3aGVyZSB0aGUgbWFnaWMgaGFwcGVucyEgVGhlIGZ1bmN0aW9uIGBkYl9pbnNlcnRfbWVhc3VyZW1lbnRzX3NtKClgIHdyaXRlcyBhbmQgcnVucyBTUUwgY29kZSB0byB1cGxvYWQgZGF0YSBmcm9tIG9uZSByb3cgb2YgdGhlIG5ld19tZWFzdXJlbWVudHMgZGF0YSBmcmFtZSBhdCBhIHRpbWUuIFNvIGBkYl9pbnNlcnRfbWVhc3VyZW1lbnRzX3NtKDEpYCB3b3VsZCBpbnNlcnQgZGF0YSBmcm9tIHRoZSBmaXJzdCByb3cuIFVzZSBhbiBhcHBseSBzdGF0ZW1lbnQgdG8gdXBsb2FkIGFsbCBvZiB0aGUgZGF0YSBhdCBvbmNlLiBUaGUgYGV2YWw9RkFMU0VgIGFyZ3VtZW50IG1lYW5zIHRoYXQgdGhpcyBjb2RlIHdpbGwgbm90IHJ1biBpZiB5b3Uga25pdCB0aGUgd2hvbGUgZG9jdW1lbnQuIAoKYGBge3IsIGV2YWw9RkFMU0V9CiMgZGJfaW5zZXJ0X21lYXN1cmVtZW50c19zbSgxKSAjIGZvciBqdXN0IG9uZSBtZWFzdXJlbWVudAoKc2FwcGx5KDE6bnJvdyhuZXdfbWVhc3VyZW1lbnRzKSwKICAgICAgIGZ1bmN0aW9uKHgpIGRiX2luc2VydF9tZWFzdXJlbWVudHNfc20oeCkpCmBgYAoKIyBRdWVyeSBkYXRhYmFzZSAKClVzZSB0aGlzIGNodW5rIHRvIG1ha2UgYSBkYXRhIGZyYW1lIGNhbGxlZCBzbV9kYiB3aXRoIGFsbCB0aGUgc29pbCBkYXRhIGluIHRoZSBkYXRhYmFzZQoKYGBge3J9CmdldF9zbV9kYXRhIDwtIGZ1bmN0aW9uKCl7CiAgCiAgc3FsIDwtIHBhc3RlMCgiU0VMRUNUIG1ydi5kYXRhdmFsdWUsIG1ydi52YWx1ZWRhdGV0aW1lLCBzZi5zYW1wbGluZ2ZlYXR1cmVjb2RlLCByLmZlYXR1cmVhY3Rpb25pZCwgdi52YXJpYWJsZWNvZGUsIHUudW5pdHNuYW1lCiBGUk9NIG9kbTIubWVhc3VyZW1lbnRyZXN1bHR2YWx1ZXMgbXJ2LCBvZG0yLnJlc3VsdHMgciwgb2RtMi52YXJpYWJsZXMgdiwgb2RtMi51bml0cyB1LCBvZG0yLnNhbXBsaW5nZmVhdHVyZXMgc2YsIG9kbTIuZmVhdHVyZWFjdGlvbnMgZmEKIFdIRVJFIHIudmFyaWFibGVpZCA9IHYudmFyaWFibGVpZCAKIEFORCByLmZlYXR1cmVhY3Rpb25pZCA9IGZhLmZlYXR1cmVhY3Rpb25pZAogQU5EIGZhLnNhbXBsaW5nZmVhdHVyZWlkID0gc2Yuc2FtcGxpbmdmZWF0dXJlaWQKIEFORCByLnVuaXRzaWQgPSB1LnVuaXRzaWQKIEFORCBtcnYucmVzdWx0aWQgPSByLnJlc3VsdGlkIAogQU5EIHIuc2FtcGxlZG1lZGl1bWN2ID0gJ1NvaWwnIikKCiAgc3FsIDwtIGdzdWIoIlxuIiwgIiIsIHNxbCkKICBkYkdldFF1ZXJ5KGRiLCBzcWwpCn0KCnNtX2RiIDwtIGdldF9zbV9kYXRhKCkKYGBgCgpvcmdhbml6ZSBiYWNrIHRvIHRoZSBvcmlnaW5hbCBmb3JtYXQgdXNpbmcgdGhlIHNwcmVhZCBmdW5jdGlvbiBpbiB0aGUgdGlkeXIgcGFja2FnZQoKYGBge3J9CnNtX2RiX3NwcmVhZCA8LSBzbV9kYiAlPiUgZ3JvdXBfYnkoc2FtcGxpbmdmZWF0dXJlY29kZSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtdW5pdHNuYW1lKSAlPiUKICBzcHJlYWQodmFyaWFibGVjb2RlLCBkYXRhdmFsdWUpCmBgYAoKYGBge3J9CnNtX2RiX3NwcmVhZCAlPiUgZGF0YXRhYmxlKCkKYGBgCgoK