*! version 1.0.0  03apr2002
program define adomaint
	version 7
	gettoken subcmd 0 : 0, parse(" ,=")

	if `"`subcmd'"' == "" {
		Query
		exit
	}
	local l = length(`"`subcmd'"')

	if "`subcmd'" == substr("list",  1, max(1,`l')) {
		List `0'
		exit
	}
	if "`subcmd'" == substr("notes", 1, max(1,`l')) {
		Notes `0'
		exit
	}
	if "`subcmd'" == substr("query", 1, max(1,`l')) {
		Query `0'
		exit
	}
	if "`subcmd'" == "rmdups" {
		Rmdups `0'
		exit
	}
	if "`subcmd'" == "update" {
		Update `0'
		exit
	}
	if "`subcmd'" == "," {
		Query, `0'
		exit
	}
	if "`subcmd'" == "_use" {
		_Use `0'
		exit
	}
	di as err "adomaint: `subcmd': unrecognized subcommand"
	exit 198
end



/*
	adomaint query
*/


program define Query, rclass
	syntax [, DIR(string) REfresh TRace]

	Fixdir dir `"`dir'"'

	*preserve
	_Use, dir(`dir') `refresh' `trace'
	local hascrawl = r(hascrawl)

	di

	qui sum idate
	ret local earlist : di %d r(min)
	ret local latest  : di %d r(max)

	ret scalar n = _N

	qui count if status==0
	ret scalar n_noinfo = r(N)

	qui count if status==1
	ret scalar n_vanished = r(N)

	qui count if status==2
	ret scalar n_dups = r(N)

	qui count if status==3
	ret scalar n_lacking = r(N)

	qui count if status==4
	ret scalar n_up_to_date = r(N)

	qui count if status==5
	ret scalar n_out_of_date = r(N)

	di as txt "{col 6}{hline 64}"
	di as txt "{col 7}number of user-written packages installed:" /*
		*/ "{col 51}" as res %9.0g return(n)



	di as txt "{col 30}earliest installed:" /*
		*/ "{col 51}{res:`return(earlist)'}"
	di as txt "{col 32}latest installed:" /*
		*/ "{col 51}{res:`return(latest)'}"

	di
	di as txt "{col 28}number of duplicates:" /*
		*/ "{col 51}" as res %9.0g return(n_dups) /*
		*/ as txt "  (note 1)"

	di as txt "{col 22}number no longer available:" /*
		*/ "{col 51}" as res %9.0g return(n_vanished) /*
		*/ as txt "  (note 2)"

	di as txt "{col 19}number known to be up-to-date:" /*
		*/ "{col 51}" as res %9.0g return(n_up_to_date) /*
		*/ as txt "  (note 3)"

	di as txt "{col 18}number known to be out-to-date:" /*
		*/ "{col 51}" as res %9.0g return(n_out_of_date) /*
		*/ as txt "  (note 4)"

	di as txt "{col 17}number lacking date information:" /*
		*/ "{col 51}" as res %9.0g return(n_lacking) /*
		*/ as txt "  (note 5)"

	di
	di as txt "{col 22}number with no information:" /*
		*/ "{col 51}" as res %9.0g return(n_noinfo) /*
		*/ as txt "  (note 6)"

	di as txt "{col 6}{hline 64}"
	di as txt `"{col 6}(type -{stata adomaint notes}- for the text of the notes)"'
end

program define Notes
	syntax
	help adomaint_notes
end


/*
	adomaint list { dups | vanished | up-to-date | out-of-date |
                        ^^^^   ^^^        ^^           ^^^

                        lacking    | noinfo | all } [, refresh trace dir()]
                        ^^^          ^^^^^^            ^^      ^^    ^^^
*/

program define List, rclass
	syntax [anything(name=cat id="package category")] /*
		*/ [, DIR(string) REfresh TRace]

	Get_cat "`cat'"
	local cat  = r(cat)

	Fixdir dir `"`dir'"'

	*preserve
	_Use, dir(`dir') `refresh' `trace'
	local hascrawl = r(hascrawl)

	if `cat'==. {
		List_cat 2 sys
		List_cat 1 sys
		List_cat 4 sys
		List_cat 5 sys
		List_cat 3 sys
		List_cat 0 sys
	}
	else 	List_cat `cat'
end

program define List_cat
	args cat sys

	if "`sys'"!="" {
		qui count if status==`cat'
		if r(N)==0 {
			exit
		}
	}

	Get_cat_title `cat'
	di as txt _n `"{title:`r(title)'}"' _n

	if "`sys'"!="" {
		qui count if status==`cat'
		if r(N)==0 {
			di as txt "    (None)"
			exit
		}
	}

	preserve
	quietly keep if status==`cat'
	sort id
	local n = _N
	forvalues i=1(1)`n' {
		local pkg = substr(pkg[`i'], 1, length(pkg[`i'])-4)
		di as txt "{p 4 8}"
		di "[" id[`i'] "] {res:`pkg'} from " as res site[`i']
		di "{p_end}"
	}
end

program define Rmdups, rclass
	syntax [, DIR(string) REfresh TRace]

	Fixdir dir `"`dir'"'

	preserve
	_Use, dir(`dir') `refresh' `trace' nomsg

	qui keep if status==2
	ret scalar n_dups = _N
	ret scalar n_removed = 0
	if _N==0 {
		di as txt "(you have no duplicates)"
		exit
	}

	local n = _N
	local cnt = 0
	forvalues i=1(1)`n' {
		di as txt "{p 0 4}
		di as txt "uninstalling " as res pkg[`i'] as txt " from"
		di as res site[`i']
		di as txt "{p_end}"
		local p : di "[*" uid[`i'] "]"
		* di "qui ado uninstall `p'"
		qui ado uninstall `p'
		local cnt = `cnt' + 1
	}
	ret scalar n_removed = `cnt'
end

program define Update, rclass
	syntax [, DIR(string) REfresh TRace]

	Fixdir dir `"`dir'"'

	preserve
	_Use, dir("`dir'") `refresh' `trace' nomsg

	qui count if status==5
	ret scalar n_out_of_date = r(N)
	ret scalar n_updated = 0
	if r(N)==0 {
		di as txt /*
		*/ "(you have no known-to-be-out-of-date packages installed)"
		exit
	}

	quietly count if status==2
	if r(N) {
		di as err "{p}"
		di as err "You have duplicate packages installed;"
		di as err "they must be removed before you"
		di as err "can use -adomaint update-."
		di as err
		local cmd1 "admaint rmdups"
		local cmd2 "admaint update"
		if `"`dir'"'!="STBPLUS" & `"`dir'"'!="SJPLUS" {
			local cmd1 `"`cmd1', dir(`dir')"'
			local cmd2 `"`cmd2', dir(`dir')"'
		}
		di as err "{p}"
		di as err `"Type "{stata `"`cmd1'"'}" and then rerun"'
		di as err `""`cmd2'"."'
		exit 459
	}


	quietly keep if status==5
	sort id

	local more : set more
	capture noisily {
		set more off
		Update_u `"`dir'"'
	}
	set more `more'
	return scalar updated = r(updated)
	exit _rc
end


program define Update_u, rclass
	args dir
	local n = _N
	local cnt = 0
	forvalues i=1(1)`n' {
		di
		di as txt "{p 0 4}
		di as txt "updating " as res pkg[`i'] as txt " from"
		di as res site[`i'] as txt ":"
		di as txt "{p_end}"

		local pkg = pkg[`i']
		local site = site[`i']
		local uid = uid[`i']
		Install_pkg `"`dir'"' `"`site'"' `"`pkg'"'
		if r(installed) {
			local cnt = `cnt' + 1
			Prove_Installed `"`dir'"' `"`site'"' `"`pkg'"' `uid'
			if r(installed)==0 {
				di as txt "    (only the date changed; code is the same)"
				Ado_uninstall `"`dir'"' "[*`uid']"
				qui Install_pkg `"`dir'"' `"`site'"' `"`pkg'"'
			}
			else {
				di as txt "    removing old package"
				Ado_uninstall `"`dir'"' [*`uid']"
			}
		}
	}
	return scalar updated = `cnt'
end



/* ------------------------------------------------------------------------ */

program define Prove_Installed, rclass
	args dir site pkg olduid

	preserve
	quietly {
		_Use, dir("`dir'") nomsg trkfile
		count if (site==`"`site'"' & pkg==`"`pkg'"') | uid=="`olduid'"
		ret scalar installed = (r(N)>=2)
	}
end



program define Install_pkg, rclass
	args dir site pkg

	ret scalar installed = 0

	capture net from `"`site'"'
	if _rc {
		di as txt "    site did not respond; skipping"
		exit
	}
	capture net describe `pkg'
	if _rc {
		di as txt "    package cannot be found at site; skipping"
		exit
	}
	di as txt "    loading update"


	Net_install `"`dir'"' `"`pkg', replace"'
	if r(rc) {
		di as txt "    package installation failed; skipping"
		exit
	}
	ret scalar installed = 1
end


program define Ado_uninstall
	args dir rest

	if "`dir'"=="SJPLUS" {
		local dir "STBPLUS"
	}
	qui ado uninstall `rest', from(`dir')
end


program define Net_install, rclass
	args dir rest

	Net_prolog `"`dir'"'

	capture net install `rest'
	ret scalar rc = _rc

	Net_epilog
end

program define Net_prolog
	args dir
	if `"`dir'"'=="SJPLUS" | `"`dir'"'=="STBPLUS" {
		qui net set ado STBPLUS
	}
	else	qui net set ado STBPLUS
end

program define Net_epilog
	args dir
	if `"`dir'"'!="SJPLUS" & `"`dir'"'!="STBPLUS" {
		qui net set ado STBPLUS
	}
end



program define Get_cat, rclass
	args cat

	if `"`cat'"'=="all" | `"`cat'"'=="" {
		ret scalar cat = .
		exit
	}

	local l = length(`"`cat'"')

	if `"`cat'"' == "dups" {
		ret scalar cat = 2
		exit
	}
	if `"`cat'"' == substr("vanished", 1, max(3,`l')) {
		ret scalar cat = 1
		exit
	}
	if `"`cat'"' == substr("up-to-date", 1, max(2,`l')) {
		ret scalar cat = 4
		exit
	}
	if `"`cat'"' == substr("out-to-date", 1, max(3,`l')) {
		ret scalar cat = 5
		exit
	}
	if `"`cat'"' == substr("lacking", 1, max(3,`l')) {
		ret scalar cat = 3
		exit
	}
	if `"`cat'"' == "noinfo" {
		ret scalar cat = 0
	}
	di as err "{p 0 4}"
	di as err `""`cat'" found where"'
	di as err "{cmd:dups},"
	di as err "{cmd:vanished},"
	di as err "{cmd:up-to-date},"
	di as err "{cmd:out-of-date},"
	di as err "{cmd:lacking},"
	di as err "{cmd:noinfo},"
	di as err "or {cmd:all} expected"
	di as err "{p_end}"
	exit 198
end


program define _Use, rclass
	syntax [, DIR(string) REfresh TRace noMSG TRKFILE LABEL]

	Fixdir dir `"`dir'"'
	Crawl_dsn crawldsn "`dir'"

	drop _all
	label drop _all
	Load_stata_trk, dir(`"`dir'"')
	if "`label'" != "" {
		label var site "trk: -net from- of pkg"
		label var id "trk: [#] used by -ado-"
		label var pkg "trk: pkg name"
		label var uid "trk: [*#] used by -ado-"
		label var idate "trk: installation date"
		label var pdate "trk: package date"
		label data `"stata.trk for `dir'"'
	}

	if "`trkfile'"!="" {
		exit
	}

	if (_N==0) {
		di as err "{p}"
		di as err "(you have no user-written ado-files installed" _c
		if `"`dir'"' "SJPLUS" {
			di as txt _n `"in `dir'"' _c
		}
		di as err ")"
		di as err "{p_end}"

		capture erase `"`crawldsn'"'
		exit 2000
	}

	if "`refresh'"!="" {
		preserve
		di as txt "{p 0 4}"
		di as txt "(contacting sites to obtain up-to-date package date information)"
		di as txt "{p_end}"
		Get_pkgs_info, `trace'
		capture drop p_date
		char _dta[crawldate] "$S_DATE"
		char _dta[crawltime] "$S_TIME"
		sort site pkg idate uid
		qui save `"`crawldsn'"', replace
		restore
	}
	capture confirm file `"`crawldsn'"'
	if _rc {
		if "`msg'" == "" {
			di as txt "{p 0 4}"
			di as txt "(no previous web search information found;"
			di as txt "you should specify the -refresh- option)"
			di as txt "{p_end}"
		}
		qui gen byte info = 0
		qui gen byte found = .
		qui gen byte c_pdate = .
		format c_pdate %d
		return scalar hascrawl = 0
		Mark_status
		if "`label'" != "" {
			Label_web_vars
		}
		exit
	}
	else	return scalar hascrawl = 1


	char _dta[crawldate]
	char _dta[crawltime]
	cap drop _merge
	sort site pkg
	qui merge site pkg using  `"`crawldsn'"', nokeep
	if "`msg'"=="" {
		Timediffmsg "`_dta[crawldate]'" "`_dta[crawltime]'"
	}
	qui gen byte info = (_merge==3)
	drop _merge
	Mark_status
	if "`label'" != "" {
		Label_web_vars
	}
end

program define Label_web_vars
	label var found "web: 1 if found, else 0"
	label var c_pdate "web: package date"
	label var info "web: 1 if found on web, else 0"
	label var status "package status"

	label define _status 0 "no info" 1 "vanished" 2 "dup" 3 "lacking" /*
			*/ 4 "up-to-date" 5 "out-of-date"
	label values status _status
end


/*
	status:
		0	no information (web info out of date)
		1	package no longer exists (maybe)
		2	package is a duplicate (should be eliminated)
		3	package exists, cannot determine up-to-dateness
		4	is known to be up-to-date
		5	is known to be out-of-date
*/

program define Get_cat_title, rclass
	args cat

	if `cat'==2 {
		ret local title "Duplicate packages"
		exit
	}
	if `cat'==1 {
		ret local title "Packages that no longer exists (or site not responding)"
		exit
	}
	if `cat'==4 {
		ret local title "Packages known to be up-to-date"
		exit
	}
	if `cat'==5 {
		ret local title "Packages known to be out-of-date"
		exit
	}
	if `cat'==3 {
		ret local title "Packages lacking date information"
		exit
	}
	if `cat'==0 {
		ret local title "Packages with no information"
		exit
	}
	exit 198
end

program define Mark_status
	quietly {
		gen byte status = 0
		replace status = 1 if info & found==0

		sort site pkg idate id
		by site pkg: replace status = 2 if _n<_N

		replace status = 3 if info & found & c_pdate==. & status!=2

		replace status = 4 if info & found & pdate!=. & c_pdate!=. & /*
				*/ pdate >= c_pdate & status!=2

		replace status = 4 if info & found & idate!=. & c_pdate!=. & /*
				*/ idate > c_pdate & status!=2

		replace status = 5 if info & found & pdate!=. & c_pdate!=. & /*
				*/ pdate < c_pdate & status!=2

		replace status = 5 if info & found & idate!=. & c_pdate!=. & /*
				*/ idate < c_pdate & status!=2
	}
end


program define Timediffmsg
	args fdate ftime

	tempname d0 d1 dif

	local cdate "$S_DATE"
	local ctime "$S_TIME"

	scalar `d0' = date("`fdate'", "dmy")*24*60 + /*
		*/ 60*real(substr("`ftime'",1,2)) + /*
		*/ real(substr("`ftime'",4,2))
	scalar `d1' = date("`cdate'", "dmy")*24*60 + /*
		*/ 60*real(substr("`ctime'",1,2)) + /*
		*/ real(substr("`ctime'",4,2))
	scalar `dif' = `d1' - `d0'

	local days = int(`dif'/(60*24))
	scalar `dif' = `dif' - `days'*60*24
	local hours = int(`dif'/60)
	scalar `dif' = `dif' - `hours'*60
	local mins = `dif'

	if `days' {
		local txt = cond(`days'>1, "`days' days, ", "1 day, ")
	}
	if `hours' {
		local sub = cond(`hours'>1, "`hours' hours, ", "1 hour, ")
		local txt "`txt'`sub'"
	}
	local sub = cond(`mins'==1, "1 minute", "`mins' minutes")
	local txt "`txt'`sub'"

	di as txt "{p 0 4}"
	di as txt "(using web information that is `txt' old)"
	di as txt "{p_end}"
end


program define Fixdir
	args dirname dir

	if `"`dir'"' == "" | `"`dir'"' == "STBPLUS" | `"`dir'"'== "SJPLUS" {
		c_local `dirname' "SJPLUS"
	}
	else if `"`dir'"' == "SITE" {
		c_local `dirname' "SITE"
	}
	else {
		di as err "dir() must be SJPLUS, STBPLUS, or SITE"
		di as err "(SJPLUS and STBPLUS mean the same thing)"
		exit 198
	}
end

program define Unfixdir
	args dirname dir

	if "`dir'"=="SJPLUS" {
		c_local `dirname' "STBPLUS"
	}
	else	c_local `dirname' "`dir'"
end


program define Crawl_dsn
	args name dir

	if `"`dir'"' == "" | `"`dir'"'=="SJPLUS" | `"`dir'"'=="STBPLUS" {
		local dir : sysdir STBPLUS
		c_local `name' `"`dir'adomaint_sjplus.dta"'
		exit
	}
	if `"`dir'"' == "SITE" {
		local dir : sysdir STBPLUS		/* sic */
		c_local `name' `"`dir'adomaint_site.dta"'
		exit
	}
	di as err `"adomaint: `dir':  must be nothing, SJPLUS, STPLUS, or SITE"'
	di as err `"(SJPLUS and STBPLUS mean the same thing)"'
	exit 198
end

/* ------------------------------------------------------------------------- */
program define Load_stata_trk
	syntax [, Dir(string) DEBUG]

	Unfixdir dir `"`dir'"'
	local dir : sysdir `"`dir'"'

	qui drop _all
	capture confirm file `"`dir'stata.trk"'
	if _rc {
		exit
	}

	quietly {
		infix str lt 1 str txt 3-83 /*
		*/ if lt=="S" | lt=="N" | lt=="D" | lt=="U" | /*
		*/ (lt=="d" & /*
                                                   ----+----1----+---
		*/ lower(substr(trim(txt),1,18))=="distribution-date:") /*
		*/ using `"`dir'stata.trk"'
		replace txt = trim(txt)
		replace txt = trim(substr(txt,19,.)) if lt=="d"
		compress
		gen long id = 1 if lt=="S"
		replace id = sum(id)
		compress id
		gen long n = _n
		compress n
		sort id n
		Make pkg N
		Make idate D
		Make pdate d
		Make uid U
		by id: keep if _n==1
		drop lt n
		rename txt site
	}

	Remake_idate, `debug'
	Remake_pdate, `debug'
end

program define Make
	args newvar ltr
	gen str80 `newvar' = txt if lt=="`ltr'"
	compress `newvar'
	by id: replace `newvar' = `newvar'[_n-1] if `newvar'==""
	by id: replace `newvar' = `newvar'[_N]
	by id: drop if lt=="`ltr'"
end

program define Remake_idate
	syntax [, DEBUG]
	tempvar x
	qui gen long `x' = date(idate, "dmy")
	format `x' %d
	if "`debug'"!="" {
		qui count if `x'==. & idate!=""
		if r(N) {
			di
			di as txt "idates missing `r(N)' cases"
			list id idate if `x'==. & idate!=""
		}
	}
	drop idate
	rename `x' idate
end

program define Remake_pdate
	syntax [, DEBUG]

	tempvar	newdate
	Translate_pdate pdate into `newdate'

	if "`debug'"!="" {
		qui count if `newdate'==. & pdate!=""
		if r(N) {
			di
			di as txt "pdates missing `r(N)' cases"
			list id pdate if `newdate'==. & pdate!=""
		}
	}
	drop pdate
	rename `newdate' pdate
end
/* ------------------------------------------------------------------------- */

program define Get_pkgs_info
	local more : set more
	cap noi {
		set more off
		Get_pkgs_info_u `0'
	}
	local rc = _rc
	set more `more'
	exit `rc'
end


program define Get_pkgs_info_u
	syntax [, TRace]

	quietly {
		sort site pkg idate id
		by site pkg: keep if _n==_N
	}

	cap drop found
	cap drop c_pdate
	qui gen byte found = 0
	qui gen long c_pdate = .
	format c_pdate %d

	local N = _N
	forvalues j=1(1)`N' {
		local site = site[`j']
		local pkg  = pkg[`j']
		Get_pkg_info `"`site'"' `"`pkg'"', `trace'
		qui replace found = r(found) in `j'
		qui replace c_pdate = r(pdate) in `j'
	}
end

/* ------------------------------------------------------------------------- */

program define Get_pkg_info, rclass
	gettoken site 0 : 0, parse(" ,")
	gettoken pkg  0 : 0, parse(" ,")
	syntax [, TRace]

	if `"`site'"' == "" {
		error 198
	}

	if `"`pkg'"' == "" {
		error 198
	}

	ret scalar pdate = .
	tempfile pfile

	if "`trace'"!="" {
		di as txt "{p 0 4}"
		di as txt `"checking `pkg' at `site'{break}"'
		di as txt `"... contacting `site'{break}"'
	}
	capture copy `"`site'/`pkg'"' `pfile', text
	if _rc {
		if "`trace'" != "" {
			di as txt "... no such pkg or site did not respond"
			di
		}
		ret scalar found = 0
		exit
	}
	if "`trace'" != "" {
		di as txt "...pkg information obtained"
		di
	}

	ret scalar found = 1

	preserve
	quietly {
		drop _all
		infix str lt 1 str txt 3-83 if (lt=="d" & /*
                                                   ----+----1----+---
		*/ lower(substr(trim(txt),1,18))=="distribution-date:") /*
		*/ using `"`pfile'"'
		replace txt = trim(txt)
		replace txt = trim(substr(txt,19,.)) if lt=="d"
		Translate_pdate txt into pdate
		if _N==0 {
			exit
		}
		drop if pdate==.
		if _N==0 {
			exit
		}
		sort pdate
		ret scalar pdate = pdate[_N]
	}
end
/* ------------------------------------------------------------------------- */
program define Translate_pdate
	version 7

	args orig into new nothing
	if "`into'" != "into" | "`nothing'"!="" {
		error 198
	}

	tempvar f x newdate


				/* ISO format yyyymmdd	*/
	qui gen byte `f' = (/*
		*	*/ substr(`orig',1,1)=="2" & real(`orig')!=. & /*
			*/ length(`orig')==8)
	qui gen str10 `x' = /*
			*/ substr(`orig',1,4) + "/" + /*
			*/ substr(`orig',5,2) + "/" + /*
			*/ substr(`orig',7,2) if `f'
	qui gen long `newdate' = date(`x', "ymd") if `f'
	drop `x' `f'

				/* house format ddMonyyyy */
	qui replace `newdate' = date(`orig', "dmy") if `newdate'==.

	format `newdate' %d
	rename `newdate' `new'
end
/* ------------------------------------------------------------------------- */