TMGPAT2  ;TMG/kst/Patching tools Suport;09/17/08
         ;;1.0;TMG-LIB;**1**;09/17/08
 ;
 ;"Kevin Toppenberg MD
 ;"GNU General Public License (GPL) applies
 ;"9/17/08

 ;"=======================================================================
 ;" API -- Public Functions.
 ;"=======================================================================
 ;"MAKFRESH(PckInit,Msg,PckDirFName) --ensure that the Package list of files avilable on server is fresh
 ;"RefreshPackge(PckInit,.Msg,NeedsRefresh,PckDirFName) -- query server for one package, and refresh info stored in TMG REMOTE PATCH SOURCE file
 ;"GetNextIENS(LastPatch,NextPatchName) -- return IENS in file TMG REMOTE PATCH SOURCE (22709)
 ;"GetIENS(PatchName) -- Given patch name, return IENS in file TMG REMOTE PATCH SOURCE (22709)
 ;"GetIENS2(PatchName) -- Given partial patch name, return IENS in file 22709.01
 ;"FORCEPAT -- All user to enter a patch entry
 ;"IsInstalled(PatchName) -- return if a given patch has already been installed.
 ;"ParsePatchName(PatchName,PckInit,Ver,PatchNum,SeqNum) -- parse a patch name into it's composit parts.
 ;"EnsureLocal(IENS,Info,Msg,Option) -- Ensure files downloaded from server and stored locally
 ;"DownloadPatch(PatchName,protocol,Option,Msg,Info) -- Ensure that the Patch has been downloaded from server and stored locally
 ;"MakePatchEntry(PatchName,Msg) -- make pseudo-entries to show that something was processed.
 ;"AddMsg(s,IsError,Msg) -- add a message to Msg Array
 ;"ShowMsg(Msg) -- display the message array

 ;"=======================================================================
 ;"Private Functions
 ;"=======================================================================
 ;"EmptyPackage(IEN9d4,Msg) - delete info for Package in file TMG REMOTE PATCH SOURCE
 ;"LoadPackage(IEN9d4,Array,protocol,Msg,SomeAdded) -- load info for Package info file TMG REMOTE PATCH SOURCE
 ;"LoadOne(PckIEN,Ver,PatchNum,SeqNum,RemoteURL,Msg) -- file one entry info TMG REMOTE PATCH SOURCE

 ;"=======================================================================
 ;"=======================================================================

MAKFRESH(PckInit,Msg,PckDirFName)
        ;"Purpose: to ensure that the Package list of files avilable on server is fresh
        ;"Input: PckInit -- this is the namespace of the package to get patches for, e.g. 'DI' for fileman
        ;"       Msg -- PASS BY REFERANCE, an OUT PARAMETER
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1
        ;"       PckDirFName -- Optional. PASS BY REFERNCE, an OUT PARAMETER. Filled with HFS filename of file
        ;"Results: none

        new IEN9d4 set IEN9d4=+$order(^DIC(9.4,"C",PckInit,""))
        if IEN9d4'>0 do  goto ENSFDone
        . do AddMsg("Can't find PACKAGE named '"_PckInit_"'",1,.Msg)
        new PckIEN,lastFMDate
        set PckIEN=+$order(^TMG(22709,"B",IEN9d4,""))
        if PckIEN>0 set lastFMDate=$piece($get(^TMG(22709,PckIEN,2)),"^",1)
        else  set lastFMDate=""
        new %,X,X1,X2,NeedsRefresh
        set NeedsRefresh=0
        if lastFMDate'="" do
        . do NOW^%DTC ;"returns date in X
        . set X1=lastFMDate,X2=X
        . do ^%DTC
        . if X>7 set NeedsRefresh=1  ;"hard code in fresh if > 7 days since last scan.
        else  set NeedsRefresh=1
        if $$RefreshPackge(PckInit,.Msg,NeedsRefresh,.PckDirFName)
ENSFDone
        quit

RefreshPackge(PckInit,Msg,NeedsRefresh,PckDirFName,Option)
        ;"Purpose: To query server for one package, and refresh info stored in TMG REMOTE PATCH SOURCE file
        ;"Input: PckInit -- this is the namespace of the package to get patches for, e.g. 'DI' for fileman
        ;"       Msg -- PASS BY REFERANCE, an OUT PARAMETER
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1
        ;"       NeedsRefresh -- 0 if refreshing not needed (just return PckDirFName, but ensure file exists)
        ;"       PckDirFName -- Optional. PASS BY REFERANCE, an OUT PARAMETER. Filled with HFS filename of file
        ;"       Option -- Optional.  PASS BY REFERANCE.
        ;"              Option("VERBOSE")=1  puts out text to console. (1 is default value)
        ;"Result : 1=success, 0=failure

        new Array,result
        new verbose set verbose=+$get(Option("VERBOSE"),1)
        set NeedsRefresh=+$get(NeedsRefresh)
        if NeedsRefresh,verbose write "Fetching list of available "_PckInit_" patches from VA ftp server..."
        set result=$$GetPckList^TMGKERNL(PckInit,.Array,.NeedsRefresh,.PckDirFName)
        if NeedsRefresh,verbose write "  Done.",!
        if $get(NeedsRefresh)'>0 goto RPDone
        if result=0 goto RPDone
        new IEN9d4 set IEN9d4=+$order(^DIC(9.4,"C",PckInit,""))
        if IEN9d4'>0 do  goto RPDone
        . do AddMsg("Can't find PACKAGE named '"_PckInit_"'",1,.Msg)
        new SomeAdded
        set result=$$LoadPackage(IEN9d4,.Array,"ftp://",.Msg,.SomeAdded)
RPDone
        quit result


EmptyPackage(IEN9d4,Msg)
        ;"Purpose: to delete info for Package in file TMG REMOTE PATCH SOURCE
        ;"Input: IEN9d4 -- IEN in 9.4 to get patches for
        ;"       Msg -- PASS BY REFERANCE, an OUT PARAMETER
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1

        new PckIEN set PckIEN=+$order(^TMG(22709,"B",IEN9d4,""))
        if PckIEN=0 goto EPDone
        new TMGFDA,TMGMSG
        set TMGFDA(22709,PckIEN_",",.01)="@"
        do FILE^DIE("EK","TMGFDA","TMGMSG")
        if $data(TMGMSG("DIERR")) do
        . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)

EPDone  quit


LoadPackage(IEN9d4,Array,protocol,Msg,SomeAdded)
        ;"Purpose: to load info for Package info file TMG REMOTE PATCH SOURCE
        ;"Input:  IEN9d4 -- IEN in 9.4 to get patches for
        ;"       Array -- This is file with available filepaths, as returned from GetPckList
        ;"       protocol -- OPTIONAL.  Default is 'ftp://'
        ;"       Msg -- PASS BY REFERANCE, an OUT PARAMETER
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1
        ;"       SomeAdded -- PASS BY REFERENCE, an OUT PARAMETER
        ;"              set to 1 if some added, otherwise 0
        ;"Result : 2=patch added, 1=no problems, 0=failure or error occured

        new result set result=1
        set SomeAdded=0
        set protocol=$get(protocol,"ftp://")
        set IEN9d4=+$get(IEN9d4)
        if IEN9d4'>0 do  goto LPDone
        . do AddMsg("Can't find record #"_IEN9d4_" in INSTALL file.",1,.Msg)
        new PckIEN set PckIEN=+$order(^TMG(22709,"B",IEN9d4,""))
        new TMGFDA,TMGMSG,TMGIEN,X,%
        do NOW^%DTC ;"output in %
        if PckIEN>0 do
        . set TMGFDA(22709,PckIEN_",",2)=%
        . do FILE^DIE("K","TMGFDA","TMGMSG")
        else  do
        . set TMGFDA(22709,"+1,",.01)=IEN9d4
        . set TMGFDA(22709,"+1,",2)=%  ;"the time downloaded
        . do UPDATE^DIE("K","TMGFDA","TMGIEN","TMGMSG")
        . set PckIEN=$get(TMGIEN(1))
        if $data(TMGMSG("DIERR")) do
        . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)
        if PckIEN'>0 goto LPDone

        new i set i=0  ;"skip first line, a header line
        for  set i=$order(Array(i)) quit:(i="")  do
        . new Name,Path,FullNamePath,Ver,PatchNum,SeqNum
        . set FullNamePath=$get(Array(i)) quit:FullNamePath=""
        . do SplitFNamePath^TMGIOUTL(FullNamePath,.Path,.Name,"/")
        . if (Path="")!(Name="") quit
        . new UName set UName=$$UP^XLFSTR(Name)
        . if UName?2.4A1"_"1.4N1"_"0.1"P"1.N1".KID" do  ;"e.g. DG_53_P481.KID or EAS_1_47.KID
        . . set Ver=$piece($piece(Name,"_",2),"_",1)
        . . new verIEN set verIEN=+$order(^TMG(22709,PckIEN,1,"C",Ver,""))
        . . if verIEN=0 set Ver="" quit
        . . set Ver=$piece($get(^TMG(22709,PckIEN,1,verIEN,0)),"^",1)
        . . if Ver="" quit
        . . set PatchNum=$piece($piece(Name,"_",3),".",1)
        . . if PatchNum?1A.N set PatchNum=$extract(PatchNum,2,99)
        . . set SeqNum="" ;"signal for no seq number provided
        . else  do
        . . set Ver=$piece($piece(Name,"_",1),"-",2) quit:(Ver="")
        . . if Ver?.N1(1"p",1"P").N set Ver=$translate(Ver,"Pp","..")
        . . set SeqNum=$piece($piece(Name,"_",2),"-",2) quit:(SeqNum="")
        . . set PatchNum=$piece($piece(Name,"_",3),"-",2) quit:(PatchNum="")
        . . set PatchNum=$piece(PatchNum,".",1) quit:(PatchNum="")
        . if Ver="" do  quit
        . . do AddMsg("Unable to process file name: "_Name_".  Couldn't determine version number.",1,.Msg)
        . ;"if SeqNum="" do  quit   ;Removed because sometimes the sequence # comes from the TXT file, not the patch file.
        . ;". do AddMsg("Unable to process file name: "_Name_".  Couldn't determine sequence number.",1,.Msg)
        . if PatchNum="" do  quit
        . . do AddMsg("Unable to process file name: "_Name_".  Couldn't determine patch number.",1,.Msg)
        . new tempResult
        . set tempResult=$$LoadOne(PckIEN,Ver,PatchNum,SeqNum,protocol_FullNamePath)
        . if tempResult=2 set SomeAdded=1

LPDone
        quit result


LoadOne(PckIEN,Ver,PatchNum,SeqNum,RemoteURL,Msg)
        ;"Purpose: to file one entry info TMG REMOTE PATCH SOURCE
        ;"         This doesn't actually get the file from the server, just store
        ;"         the directory info, for later retrieval
        ;"Input: PckIEN -- the IEN in TMG REMOTE PATCH SOURCE for Package
        ;"       Ver -- the version of the patch
        ;"       PatchNum -- the patch number
        ;"       SeqNum -- the patch sequence number (the release sequence number) (if provided)
        ;"       RemoteURL -- The protocol_remoteURL of the patch on the server
        ;"       Msg -- PASS BY REFERANCE, an OUT PARAMETER
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1
        ;"Result : 1=success, 0=failure

        new result set result=0 ;"default to failure
        set PckIEN=+$get(PckIEN) if PckIEN'>0 goto LODone
        set Ver=$get(Ver) if Ver="" goto LODone
        if Ver'["." set Ver=Ver_".0"
        set PatchNum=$get(PatchNum) if PatchNum="" goto LODone
        set SeqNum=$get(SeqNum) ;"OK if no sequence number
        new VerIEN set VerIEN=+$order(^TMG(22709,PckIEN,1,"B",Ver,""))
        if VerIEN'>0 do
        . new TMGFDA,TMGIEN,TMGMSG
        . set TMGFDA(22709.01,"+1,"_PckIEN_",",".01")=Ver
        . set TMGFDA(22709.01,"+1,"_PckIEN_",",".02")=$translate(Ver,".","") ;"synonym
        . do UPDATE^DIE("","TMGFDA","TMGIEN","TMGMSG")
        . if $data(TMGMSG("DIERR")) do
        . . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)
        . set VerIEN=+$get(TMGIEN(1))
        if VerIEN'>0 goto LODone
        new patchIEN set patchIEN=+$order(^TMG(22709,PckIEN,1,VerIEN,1,"B",PatchNum,""))
        if patchIEN'>0 do
        . new TMGFDA,TMGIEN,TMGMSG
        . set TMGFDA(22709.11,"+1,"_VerIEN_","_PckIEN_",",".01")=PatchNum
        . do UPDATE^DIE("","TMGFDA","TMGIEN","TMGMSG")
        . if $data(TMGMSG("DIERR")) do
        . . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)
        . set patchIEN=+$get(TMGIEN(1))
        . set result=2  ;"something added
        if patchIEN'>0 goto LODone
        new TMGFDA,TMGMSG
        new Ext set Ext=$piece(RemoteURL,".",$length(RemoteURL,"."))

        new spec
        set spec("'")="'\''",spec("*")="'\*'",spec("&")="'\&'",spec("?")="'\?'"
        set spec("\ ")=" "
        set RemoteURL=$$REPLACE^XLFSTR(RemoteURL,.spec)

        new field
        if $$UP^XLFSTR(Ext)="TXT" set field=1.5
        else  set field=1
        set TMGFDA(22709.11,patchIEN_","_VerIEN_","_PckIEN_",",field)=RemoteURL
        if SeqNum'="" set TMGFDA(22709.11,patchIEN_","_VerIEN_","_PckIEN_",",".02")=SeqNum
        do FILE^DIE("K","TMGFDA","TMGMSG")
        if $data(TMGMSG("DIERR")) do
        . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)
        if $data(TMGMSG("DIERR"))=0 do
        . if result>0 quit
        . set result=1 ;"success
LODone
        quit result


GetNextIENS(LastPatch,NextPatchName)
        ;"Purpose: Given last patch name, return IENS in file TMG REMOTE PATCH SOURCE (22709)
        ;"         that points to record with information about next appropriate patch.
        ;"Input: LastPatch -- expected Format: e.g.  DI*22.0*100 SEQ #123
        ;"       NextPatchName -- PASS BY REFERENCE, an OUT PARAMETER
        ;"                              Will be filled with name of next patch.
        ;"Output: returns IENS to record in 22709.11, or "" if problem.

        new result set result=""
        set NextPatchName=""
        if $get(LastPatch)="" goto GNPDone

        new seqNum,PckInit,Ver,PatchNum,seqNum
        do ParsePatchName(LastPatch,.PckInit,.Ver,.PatchNum,.seqNum)
        if seqNum="" goto GNPDone

        new IEN9d4 set IEN9d4=+$order(^DIC(9.4,"C",PckInit,""))
        if IEN9d4'>0 goto GNPDone
        new PckIEN set PckIEN=+$order(^TMG(22709,"B",IEN9d4,""))
        if PckIEN'>0 goto GNPDone
        new VerIEN set VerIEN=+$order(^TMG(22709,PckIEN,1,"B",Ver,""))
        if VerIEN'>0 set VerIEN=+$order(^TMG(22709,PckIEN,1,"B",$piece(Ver,".0",1),""))
        if VerIEN'>0 goto GNPDone

        new patchIEN,nextSeq
        set nextSeq=+$order(^TMG(22709,PckIEN,VerIEN,"SEQ",seqNum))
        if nextSeq'>0 goto GNPDone
        set patchIEN=+$order(^TMG(22709,PckIEN,VerIEN,"SEQ",nextSeq,""))
        if patchIEN'>0 goto GNPDone
        set result=patchIEN_","_VerIEN_","_PckIEN_","
        set NextPatchName=$piece(LastPatch," ",1)
        new node0 set node0=$get(^TMG(22709,PckIEN,1,VerIEN,1,patchIEN,0))
        new nextPatchNum set nextPatchNum=$piece(node0,"^",1)
        set $piece(NextPatchName,"*",3)=nextPatchNum
        set NextPatchName=NextPatchName_" SEQ #"_nextSeq

GNPDone
        quit result


GetIENS(PatchName)
        ;"Purpose: Given patch name, return IENS in file TMG REMOTE PATCH SOURCE (22709)
        ;"Input: PatchName -- expected Format: e.g.  DI*22.0*100 SEQ #123
        ;"Output: returns IENS to record in 22709.11, or "" if problem.

        new result set result=""
        if $get(PatchName)="" goto GIDone
        new seqNum,PckInit,Ver,PatchNum,seqNum
        new IEN9d4,PckIEN,VerIEN,patchIEN
        do ParsePatchName(PatchName,.PckInit,.Ver,.PatchNum,.seqNum) goto:(seqNum="") GIDone
        set IEN9d4=+$order(^DIC(9.4,"C",PckInit,"")) goto:(IEN9d4'>0) GIDone
        set PckIEN=+$order(^TMG(22709,"B",IEN9d4,"")) goto:(PckIEN'>0) GIDone
        set VerIEN=+$order(^TMG(22709,PckIEN,1,"B",Ver,""))
        if VerIEN'>0 set VerIEN=+$order(^TMG(22709,PckIEN,1,"B",$piece(Ver,".0",1),""))
        if VerIEN'>0 goto GIDone
        set patchIEN=+$order(^TMG(22709,PckIEN,VerIEN,"SEQ",seqNum,"")) goto:(patchIEN'>0) GIDone
        set result=patchIEN_","_VerIEN_","_PckIEN_","
GIDone
        quit result


GetIENS2(PatchName)
        ;"Purpose: Given partial patch name, return IENS in file 22709.01
        ;"         (i.e. just patch*ver)
        ;"Input: PatchName -- expected Format: e.g.  DI*22.0*.... or DI*22.0
        ;"Output: returns IENS to record in 22709.01, or "" if problem.

        new result set result=""
        if $get(PatchName)="" goto GI2Done
        new seqNum,PckInit,Ver,PatchNum,seqNum
        new IEN9d4,PckIEN,VerIEN,patchIEN
        do ParsePatchName(PatchName,.PckInit,.Ver,.PatchNum,.seqNum)
        if (PckInit="")!(Ver="") goto GI2Done
        set IEN9d4=+$order(^DIC(9.4,"C",PckInit,"")) goto:(IEN9d4'>0) GI2Done
        set PckIEN=+$order(^TMG(22709,"B",IEN9d4,"")) goto:(PckIEN'>0) GI2Done
        set VerIEN=+$order(^TMG(22709,PckIEN,1,"B",Ver,""))
        if VerIEN'>0 set VerIEN=+$order(^TMG(22709,PckIEN,1,"B",$piece(Ver,".0",1),""))
        if VerIEN'>0 goto GI2Done
        set result=VerIEN_","_PckIEN_","
GI2Done
        quit result


EnsureLocal(IENS,Info,Msg,Option)
        ;"Purpose: Ensure that the files have been downloaded from server and stored locally
        ;"Input: IENS -- IENS in 22709.11
        ;"       Info -- PASS BY REFERENCE, an OUT PARAMETER.
        ;"       Msg -- PASS BY REFERANCE, an OUT PARAMETER
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1
        ;"       Option -- optional.  Pass by reference.
        ;"              Option("VERBOSE")=1, means messaages also written directly to output
        ;"Output: Info will be filled as follows:
        ;"              Info("PATH")=Path in HFS
        ;"              Info("KID FILE")=HFS filename of .KID patch
        ;"              Info("TEXT FILE")=HFS filename of .TXT accompanying patch
        ;"              Info("TEXT ONLY")=1 if there is a text file, but no .KIDS file
        ;"              Info("KID URL")=URL on server for KID file
        ;"              Info("TEXT URL")=URL on server for TXT file
        ;"results: 1 if OK, 0 if problem.

        new TMGMSG,TMGDATA,TMGFDA
        new verbose set verbose=$get(Option("VERBOSE"))
        new result set result=1  ;"default to success
        kill Info
        if $get(IENS)="" goto ELDone
        do GETS^DIQ(22709.11,IENS,"1;1.5;2;3;4","","TMGDATA","TMGMSG")
        if $data(TMGMSG("DIERR")) do  goto ELDone
        . new tempS set tempS=$$GetErrStr^TMGDEBUG(.TMGMSG)
        . do AddMsg(tempS,1,.Msg)
        . if verbose write tempS,!
        new URL set URL=$get(TMGDATA(22709.11,IENS,1))
        set Info("KID URL")=URL
        new textURL set textURL=$get(TMGDATA(22709.11,IENS,1.5))
        set Info("TEXT URL")=textURL
        if URL="" do
        . if textURL'="" set Info("TEXT ONLY")=1 quit
        . new tempS set tempS="No URL found for KIDS patch or accompanying Info text file in FM File #22709.22, IENS="_IENS
        . do AddMsg(tempS,1,.Msg)
        . if verbose write tempS,!

        new Path set Path=$get(TMGDATA(22709.11,IENS,2))
        if Path="" do
        . set Path=$get(^TMG("KIDS","PATCH DIR"),"/tmp/")
        . set TMGFDA(22709.11,IENS,2)=Path
        set Info("PATH")=Path
        new Filename set Filename=$get(TMGDATA(22709.11,IENS,3))
        new textFilename set textFilename=$get(TMGDATA(22709.11,IENS,4))

        if (Filename'=""),$$FileExists^TMGIOUTL(Path_Filename) do
        . set Info("KID FILE")=Filename
        else  if (URL'="") do
        . if verbose write "Downloading KID file from FTP.VA.GOV..."
        . if $$DownloadFile^TMGKERNL(URL,Path)
        . set Filename=$$FNameExtract^TMGIOUTL(URL)
        . if $$FileExists^TMGIOUTL(Path_Filename) do
        . . if $$Dos2Unix^TMGKERNL(Path_Filename)
        . . set TMGFDA(22709.11,IENS,3)=Filename
        . . set Info("KID FILE")=Filename
        . else  set result=0
        . if verbose write !

        if (textFilename'=""),$$FileExists^TMGIOUTL(Path_textFilename) do
        . set Info("TEXT FILE")=textFilename
        else  if (textURL'="") do
        . if verbose write "Downloading TEXT file from FTP.VA.GOV..."
        . if $$DownloadFile^TMGKERNL(textURL,Path)
        . set textFilename=$$FNameExtract^TMGIOUTL(textURL)
        . if $$FileExists^TMGIOUTL(Path_textFilename) do
        . . set TMGFDA(22709.11,IENS,4)=textFilename
        . . set Info("TEXT FILE")=textFilename
        . set result=0
        . if verbose write !

        set result=1  ;"success
ELDone
        kill TMGMSG
        if $data(TMGFDA) do
        . do FILE^DIE("","TMGFDA","TMGMSG")
        . if $data(TMGMSG("DIERR")) do
        . . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)
        . . set result=0
        . . if verbose write Msg("ERROR",MsgI),!

        quit result


DownloadPatch(PatchName,protocol,Option,Msg,Info)
        ;"Purpose: Ensure that the Patch has been downloaded from server and stored locally
        ;"Input: PatchName -- the name of the patch to get, e.g. ABC*12.34*1234 [SEQ #123]
        ;"       protocol -- OPTIONAL.  Default is 'ftp://'
        ;"       Option -- optional.  Pass by reference.
        ;"              Option("VERBOSE")=1, means messaages also written directly to output
        ;"       Msg -- PASS BY REFERANCE, an OUT PARAMETER
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1
        ;"       Info -- PASS BY REFERENCE, an IN and OUT PARAMETER.
        ;"              Info("PATH")=Path in HFS
        ;"              Info("KID FILE")=HFS filename of .KID patch
        ;"              Info("TEXT FILE")=HFS filename of .TXT accompanying patch
        ;"              Info("TEXT ONLY")=1 if there is a text file, but no .KIDS file
        ;"              Info("KID URL")=URL on server for KID file
        ;"              Info("TEXT URL")=URL on server for TXT file
        ;"Output: Info will be filled as follows:
        ;"              Info("PATH")=Path in HFS
        ;"              Info("KID FILE")=HFS filename of .KID patch
        ;"results: 1 if OK, 0 if problem.

        new result set result=1
        new seqNum,PckInit,Ver,PatchNum,seqNum,PckDirFName,URL
        new verbose set verbose=($get(Option("VERBOSE"))=1)
        set protocol=$get(protocol,"ftp://")
        if PatchName?2.4N1"*"1.3N.(1"."1.4N)1"*"1.4N do
        . do ParsePatchName(PatchName,.PckInit,.Ver,.PatchNum,.seqNum)
        else  if PatchName?1.4A1"_".E do
        . set PckInit=$piece(PatchName,"_",1)
        else  do  goto:(result=0) DLPDone
        . new tempName set tempName=$get(Info("TEXT FILE"))
        . if tempName="" set result=0 quit
        . if tempName?1.4A1"_".E do
        . . set PckInit=$piece(tempName,"_",1)
        . else  if tempName?1.4A1"-".E do
        . . set PckInit=$piece(tempName,"-",1)
        . if $get(PckInit)="" set result=0

        set result=$$RefreshPackge(PckInit,.Msg,0,.PckDirFName) goto:(result=0) DLPDone
        set result=$$FindMultPatch^TMGPAT4(PatchName,PckInit,.Option,.URL,.Info) goto:(result=0) DLPDone

        new Filename,Path
        set Path=$get(^TMG("KIDS","PATCH DIR"),"/tmp/")
        set Info("PATH")=Path
        set Filename=$$FNameExtract^TMGIOUTL(URL)
        if $$FileExists^TMGIOUTL(Path_Filename) goto DLPDone
        if verbose write "Downloading "_Filename_" from FTP.VA.GOV...",!
        new spec set spec("\ ")="%20"
        set URL=$$REPLACE^XLFSTR(URL,.spec)
        if $$DownloadFile^TMGKERNL(protocol_URL,Path,0)
        if $$FileExists^TMGIOUTL(Path_Filename)=0 set result=0 goto DLPDone
        if $$Dos2Unix^TMGKERNL(Path_Filename)
        set Info("KID FILE")=Filename
DLPDone
        quit result

FORCEPAT
        ;"Purpose: All user to enter a patch entry

        new PckInit,Ver,DIR,PatchNum,SeqNum,NewPatch
        do GetPckVer^TMGPAT1(.PckInit,.Ver)
        set DIR(0)="N",DIR("A")="Enter PATCH NUMBER"
        do ^DIR write !
        set PatchNum=Y
        if $get(DIRUT) goto FPDone
        set DIR(0)="N",DIR("A")="Enter SEQUENCE NUMBER"
        do ^DIR write !
        set SeqNum=Y
        if $get(DIRUT) goto FPDone
        do ForceP2(.PckInit,.Ver,.PatchNum,.SeqNum)
        quit

ForceP2(PckInit,Ver,PatchNum,SeqNum)
        ;"Purpose: Hack write a patch into Package file, based in componant parts.
        ;"Results: None

        new NewPatch set NewPatch=PckInit_"*"_Ver_"*"_PatchNum_" SEQ #"_SeqNum
        new DIR set DIR(0)="Y"
        set DIR("A")="HACK/FORCE an entry in the Package file for: "_NewPatch_" (Y/N)"
        do ^DIR write !
        if $get(DIRUT)!(Y'=1) goto FPDone
        new Msg
        if $$MakePatchEntry^TMGPAT2(NewPatch,.Msg)
        if $$ShowMsg^TMGPAT2(.Msg)
FPDone
        quit


MakePatchEntry(PatchName,Msg)
        ;"Purpose: For times when a patch was informational only, and there was
        ;"         no KIDS file to actually install, then this can make pseudo-entries
        ;"         to show that something was processed.
        ;"Input: PatchName -- The name of the patch.  Eg: DI*22*123 SEQ #456"
        ;"       Msg -- PASS BY REFERANCE, an OUT PARAMETER
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1

        ;"Results: 1 if OK, 0 if error

        new %,X,result
        new TMGMSG,TMGFDA,TMGIEN
        set result=1
        new justPatch set justPatch=$piece(PatchName," SEQ",1)
        new tempIEN set tempIEN=+$order(^XPD(9.7,"B",justPatch,""))
        if tempIEN>0 goto MPE2 ;"INSTALL entry already made

        do NOW^%DTC
        set TMGFDA(9.7,"+1,",.01)=justPatch
        set TMGFDA(9.7,"+1,",.02)=3                     ;"2 = status
        set TMGFDA(9.7,"+1,",6)="Text_Only "_PatchName  ;"6 = file comment
        set TMGFDA(9.7,"+1,",9)=DUZ                     ;"9 = Installed by
        set TMGFDA(9.7,"+1,",11)=%                      ;"11 = Install start time
        set TMGFDA(9.7,"+1,",17)=%                      ;"17 = Install completion time
        do UPDATE^DIE("","TMGFDA","TMGIEN","TMGMSG")
        if $data(TMGMSG("DIERR")) do
        . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)
        . set result=0

MPE2    set PckInit=$piece(PatchName,"*",1)
        set Ver=$piece(PatchName,"*",2)
        if (PckInit="")!(Ver="") set result=0 goto MPEDone
        new IEN9d4,IEN9d49
        set IEN9d4=+$order(^DIC(9.4,"C",PckInit,""))
        if IEN9d4'>0 set result=0 goto MPEDone
        set IEN9d49=+$order(^DIC(9.4,IEN9d4,22,"B",Ver,""))
        if IEN9d49'>0 set result=0 goto MPEDone

        new PatchSeq set PatchSeq=$piece(PatchName,"*",3)
        set tempIEN=$order(^DIC(9.4,IEN9d4,22,IEN9d49,"PAH","B",PatchSeq,""))
        if tempIEN>0 do  goto MPE3
        . set TMGIEN(1)=tempIEN
        new IENS set IENS="+1,"_IEN9d49_","_IEN9d4_","
        kill TMGFDA,TMGMSG,TMGIEN
        set TMGFDA(9.4901,IENS,.01)=PatchSeq ;".01=Patch Hx, e.g. 10 SEQ #10
        set TMGFDA(9.4901,IENS,.02)="NOW"   ;".02=date applied
        set TMGFDA(9.4901,IENS,.03)="`"_DUZ ;".03=Applied by
        do UPDATE^DIE("E","TMGFDA","TMGIEN","TMGMSG")
        if $data(TMGMSG("DIERR")) do
        . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)
        . set result=0

MPE3    if result=0 goto MPEDone
        new TMGWP
        kill TMGFDA,TMGMSG
        set TMGWP(1)="Patch was informational only.  No installed code etc."
        set TMGWP(2)="This entry was created as a marker that information was processed."
        set IENS=TMGIEN(1)_","_IEN9d49_","_IEN9d4_","
        do WP^DIE(9.4901,IENS,1,"","TMGWP","TMGMSG")
        if $data(TMGMSG("DIERR")) do
        . do AddMsg($$GetErrStr^TMGDEBUG(.TMGMSG),1,.Msg)
        . set result=0

MPEDone
        quit result

IsInstalled(PatchName)
        ;"Purpose: To return if a given patch has already been installed.
        ;"Input: PatchName -- format aaaa*nn.nn*mmm [SEQ #xxx]
        ;"Result: 1 if installed, 0 if not, or problem.

        new result set result=0
        new PckInit,Ver,PatchNum

        set PatchName=$piece(PatchName," ",1)
        set PatchName=$$TrimRType^TMGSTUTL(PatchName,"C") ;"trim any characters off end of patch name, e.g. 'ABC*5.5*123<=='
        set PckInit=$piece(PatchName,"*",1)
        set Ver=$piece(PatchName,"*",2)
        set PatchNum=$piece(PatchName,"*",3)
        if (PckInit="")!(Ver="")!(PatchNum="") goto IIDone

        new IEN9d4,IEN9d49
        set IEN9d4=+$order(^DIC(9.4,"C",PckInit,""))
        if IEN9d4'>0 goto IIDone
        set IEN9d49=+$order(^DIC(9.4,IEN9d4,22,"B",Ver,""))
        if IEN9d49'>0 do
        . new oneVer set oneVer=""
        . new found set found=0
        . for  set oneVer=$order(^DIC(9.4,IEN9d4,22,"B",oneVer)) quit:(oneVer="")!found  do
        . . new Int,oneInt,Dec,oneDec
        . . set Int=$piece(Ver,".",1),Dec=+$piece(Ver,".",2)
        . . set oneInt=$piece(oneVer,".",1),oneDec=+$piece(oneVer,".",2)
        . . if (Int=oneInt)&(Dec=oneDec) do
        . . . set IEN9d49=+$order(^DIC(9.4,IEN9d4,22,"B",oneVer,""))
        . . . set found=1
        if IEN9d49'>0 goto IIDone

        goto:(IEN9d49'>0) IIDone

        new i,array,done
        set i="",done=0
        for  set i=$order(^DIC(9.4,IEN9d4,22,IEN9d49,"PAH","B",i)) quit:(i="")  do
        . new onePatchNum set onePatchNum=$piece(i," ",1)
        . if onePatchNum=PatchNum do
        . . set result=1,done=1

IIDone
        quit result


ParsePatchName(PatchName,PckInit,Ver,PatchNum,SeqNum)
        ;"Purpose: to parse a patch name into it's composit parts.
        ;"Input: PatchName -- the patch name to parse, e.g. ABC*12.34*1234 SEQ #123
        ;"       PckInit,Ver,PatchNum,SeqNum -- PASS BY REFERENCE, OUT PARAMETERS
        ;"Results: none
        set SeqNum=+$piece(PatchName,"SEQ #",2)
        set PatchName=$piece(PatchName," SEQ #",1)
        set PckInit=$piece(PatchName,"*",1)
        set Ver=$piece(PatchName,"*",2)
        set PatchNum=$piece(PatchName,"*",3)
        quit



AddMsg(s,IsError,Msg)
        ;"Purpose: to add a message to Msg Array
        ;"Input: s  -- message.  May be a string, or an array (or both) in format of:
        ;"              s=A line
        ;"              s(1)=line 1
        ;"              s(2)=line 2
        ;"       IsError -- 1 if is an error message
        ;"       Msg -- PASS BY REFERENCE, an OUT PARAMETER.
        ;"              Errors are stored in Msg("ERROR",x)=Message
        ;"                                   Msg("ERROR")=count of last error
        ;"              Message are store in Msg(x)=Message
        ;"                                   Msg=count of last message+1
        ;"Results: none

        set IsError=+$get(IsError)
        new oneLine,subI set subI=""
        set oneLine=$get(s)
        for  do  set subI=$order(s(subI)) quit:(subI="")  set oneLine=$get(s(subI))
        . if IsError do
        . . new MsgI set MsgI=$get(Msg("ERROR"),0)+1
        . . set Msg("ERROR",MsgI)=oneLine
        . . set Msg("ERROR")=MsgI
        . else  do
        . . set Msg=+$get(Msg,1)
        . . set Msg(Msg)=oneLine,Msg=Msg+1
        quit


ShowMsg(Msg,NoPause)
        ;"Purpose: to display the message array
        ;"Input: Msg - PASS BY REFERENCE.  The message array to display.
        ;"       NoPause -- OPTIONAL.  If 1, then user not prompted to hit enter to cont.
        ;"Results: 0 if OK, 1 if ERROR found in message array.
        new errorFound set errorFound=0
        if $data(Msg) do
        . new i set i=""
        . for  set i=$order(Msg(i)) quit:(+i'>0)  write "  ",$get(Msg(i)),!
        . if $data(Msg("ERROR")) do
        . . write !!,"NOTE: ERRORS ENCOUNTERED:",!
        . . set i=""
        . . for  set i=$order(Msg("ERROR",i)) quit:(+i'>0)  write "  ",$get(Msg("ERROR",i)),!
        . . set errorFound=1
        . if $get(NoPause)'=1 do PressToCont^TMGUSRIF

        quit errorFound