!fnfileage [106] - functions related to file age 
!------------------------------------------------------------------------
!EDIT HISTORY
!Version 1.0:-
! [106] 11-Mar-26 / jdm / Add Fn'FileAgeLatest$(file$,limit)
! [105] 13-Jan-25 / jdm / Add Fn'FileAgeDate$(file$,flags) - display file date
! [104] 22-Jun-19 / jdm / Adjust debug.print statements 
! [103] 26-Feb-18 / jdm / Add Fn'FileAgeDiff(file1,file2,flags) - age diff in secs;
!                           ++include fnextch.bsi
! [102] 23-Sep-15 / jdm / Add :inputonly, minor modernization
! [101] 23-Oct-14 / jdm / Add ++ifndef
! [100] 10-Sep-07 / jdm / Create Fn'FileAge(file,flags) - age in sec
!----------------------------------------------------------------------
!ROUTINES:
! Fn'FileAge(file$, flags) - return age of file
! Fn'FileAgeDiff(file1$,file2$,flags) - file1 age minus file2 age (secs)
! Fn'FileAgeDate$(file$,flags) - display file date 
! Fn'FileAgeLatest$(file$,limit) - find latest copy of file{-##}.ext
!---------------------------------------------------------------------
!Keywords: date, time, file
!---------------------------------------------------------------------
!Also see:
!   fndatetime.bsi (general date/time routines)
!   fndtoffset.bsi (date offset by days/hours)
!---------------------------------------------------------------------

++ifndef FILEAGE_MTIME

++include'once ashinc:ashell.def
++include'once sosfunc:fnextch.bsi
++include'once sosfunc:fnfqfs.bsi       ! [106]
++include'once sosfunc:fnexplode.bsi    ! [106]

  ! Fn'FileAge flags
  define FILEAGE_MTIME  = &h0000        ! use last mod time
  define FILEAGE_CTIME  = &h0001        ! use create time
  define FILEAGE_REMPC  = &h0002        ! file(s) reside(s) on remote ATE PC

!---------------------------------------------------------------------
!Function:
!   Return age of file (in secs)
!Params:
!   Filespec [string, in] - AMOS or native spec of file
!   Flags [num, in, optional] : which date to use
!        FILEAGE_MTIME = last modify time (default)
!        FILEAGE_CTIME = create time
!        FILEAGE_REMPC = file resides on remote (ATE) PC
!Returns:
!   # secs since file last modified (or created)
!   (-1 if file not found)
!   (-2 if file on remote pc and you don't have ATE)
!Globals:
!   No global vars
!Locals:
!   No other local vars
!Error handling:
!   Traps errors, returns err(0) value as status code
!Notes:
!   Theoretically we could figure this out without creating a
!   reference file, but the code shown below to do so is off
!   by what appears to be a GMT offset.  Since I'm not sure
!   how to determine that for the local filesystem, we take the
!   easy way out and create a reference file to get the "now"
!   time.  To avoid having to do this every time (especially
!   in progs like DIR which might do it repetitively, we use
!   a pair of static vars to keep track of our reference time.)
!---------------------------------------------------------------------
Function Fn'FileAge(fspec$ as s260:inputonly, flags as b4:inputonly) as i4

    Static map1 s_midnight,b,4        ! reference time of prev midnight
    Static map1 s_today,b,4           ! reference day

    map1 locals
        map2 locrem$,s,1
        map2 bytes,f
        map2 mtime,b,4
        map2 ctime,b,4
        map2 now,b,4
        map2 ch,b,2
        map2 nowfile$,s,16,"fnfileage.tmp"

    if (flags and FILEAGE_REMPC)
        locrem$ = "R"
    else
        locrem$ = "L"
    endif

    ! due to some confusion about how to calibrate the local file times,
    ! just take the easy way out and create a file now in order to get
    ! the "now" time
    if (s_midnight=0 or s_today#DATE) then
        ch = Fn'NextCh(62000)
        open #ch,nowfile$,output
        close #ch
        xcall MIAMEX, MX_FILESTATS, "L", nowfile$, bytes, now
        kill nowfile$
        s_midnight = now - TIME
        s_today = DATE
        debug.print (99, "fnfileage") "Reference time: now=["+now+"], midnight=["+s_midnight+"], today=["+s_today+"]"
    else
        now = s_midnight + TIME ! update now value based on seconds clock
    endif

    xcall MIAMEX, MX_FILESTATS, locrem$, fspec$, bytes, mtime, ctime
    debug.print (99, "fnfileage") "stats on "+fspec$+" - bytes: "+bytes+", mtime: "+mtime+", ctime: "+ctime

    if (flags and FILEAGE_CTIME) then
        mtime = ctime
    endif

! Alternate method to calculate now
!    if now = 0 then
!        ! get 'now' time
!        now = DATE
!        DEBUG.PRINT "today (sep): now=["+now+"] "
!        xcall DSTOI, now    ! convert from sep to days since 1/1/00
!        DEBUG.PRINT "dstoi: now=["+now+"] "
!        now = now - 25567   ! subtract 1/1/1970 value
!        DEBUG.PRINT "since epoch: now=["+now+"] "
!        now = now * 86400   ! convert days to secs
!        DEBUG.PRINT "secs: now=["+now+"] "
!        now = now + TIME    ! add secs since midnight
!        DEBUG.PRINT "time=["+time+"], +time: now=["+now+"] "
!
        ! add GMT factor? (7 hours for me???)
        !    now = now + 25200
!    endif

    if bytes >= 0 then
        Fn'FileAge = now - mtime
    else
        Fn'FileAge = bytes
    endif

EndFunction


!---------------------------------------------------------------------
!Function:
!   Fn'FileAgeDiff - return age diff between two files (in secs)
!Params:
!   file1$  (str) [in] - first file
!   file2$  (str) [in] - second file
!   flags [num, in, optional] : which date to use
!        FILEAGE_MTIME = last modify time
!        FILEAGE_CTIME = create time
!        FILEAGE_REMPC = files reside on remote (ATE) PC
!   filecount (num) [out] - # of files found (0, 1 or 2)
!                           -1 indicates no ATE support for FILEAGE_REMPC
!Returns:
!   file1 date minus file2 date (if > 0, file1 newer than file2)
!   Returns 0 if both files missing (see filecount arg)
!Globals:
!Notes:
!   A missing file is treated as having date zero, so if file1 exists
!   and file2 is missing, return value will be the mtime (or ctime)
!   of file1. In the reverse case, it will be negative mtime or ctime.
!   If both are missing, return value is 0. Use filecount arg to 
!   check for that condition if you care.
!---------------------------------------------------------------------
Function Fn'FileAgeDiff(fspec1$ as s260:inputonly, fspec2$ as s260:inputonly, &
                        flags as b4:inputonly, filecount as i2:outputonly) as i4
    map1 locals
        map2 locrem$,s,1
        map2 bytes1,f
        map2 bytes2,f
        map2 mtime1,b,4
        map2 ctime1,b,4
        map2 mtime2,b,4
        map2 ctime2,b,4

    if (flags and FILEAGE_REMPC)
        locrem$ = "R"
    else
        locrem$ = "L"
    endif

    xcall MIAMEX, MX_FILESTATS, locrem$, fspec1$, bytes1, mtime1, ctime1
    debug.print (99, "fnfileage") "stats on "+fspec1$+" - bytes: "+bytes1+", mtime: "+mtime1+", ctime: "+ctime1

    if (bytes1 = -2) then           ! no ATE support for FILEAGE_REMPC
        filecount = -1
        Fn'FileAgeDiff = 0
        exitfunction
    endif

    xcall MIAMEX, MX_FILESTATS, locrem$, fspec2$, bytes2, mtime2, ctime2
    debug.print (99, "fnfileage") "stats on "+fspec2$+" - bytes: "+bytes2+", mtime: "+mtime2+", ctime: "+ctime2

    filecount = 2
    if (flags and FILEAGE_CTIME) then
        mtime1 = ctime1
        mtime2 = ctime2
    endif
    if (bytes1 < 0)
        mtime1 = 0
        filecount -= 1
    endif
    if (bytes2 < 0)
        mtime2 = 0
        filecount -= 1
    endif

    Fn'FileAgeDiff = mtime1 - mtime2
    xputarg @filecount
    debug.print (99, "fnfileage") "Fn'FileAgeDiff("+fspec1$+","+fspec2$+") ="+Fn'FileAgeDiff
EndFunction

!---------------------------------------------------------------------
!Function:
!   Return formatted file date
!Params:
!   Filespec [string, in] - AMOS or native spec of file
!   Flags [num, in, optional] : which date to use
!        FILEAGE_MTIME = last modify time (default)
!        FILEAGE_CTIME = create time
!Returns:
!   "Sun Jul 31 16:02:41 2011"
!Globals:
!   No global vars
!Locals:
!   No other local vars
!Error handling:
!   Traps errors, returns err(0) value as status code
!Notes:
!---------------------------------------------------------------------
Function Fn'FileAgeDate$(fspec$ as s260:inputonly, flags as b4:inputonly) as s24

    map1 locals
        map2 bytes,i,4
        map2 mtime,b,6
        map2 ctime,b,6
        
    xcall MIAMEX, MX_FILESTATS, "L", fspec$, bytes, mtime, ctime
    if flags and FILEAGE_CTIME then
        xcall MIAMEX, MX_FTFORMAT, ctime, .fn
    else
        xcall MIAMEX, MX_FTFORMAT, mtime, .fn
    endif
endfunction


![106]
!---------------------------------------------------------------------
!Function:
!   Return the latest version of the specified input file, based on
!   -# suffixes and timestamp. 
!Params:
!   fspec$  (str) [in] - DevPPN or native filespec, e.g. file.ext, 
!                          /path/file.ext, ersatz:file.ext, file.exe[p,pn], etc.
!                          Do not include *.  See Examples below
!   limit (num) [in] - Used only under Windows if fspec$ is a native path
!                          to limit the number of -## suffixes exampled (default 20)
!Returns:
!   possibly updated fspec$ in fully qualified native format (e.g. /path/file-3.ext)
!Globals:
!Notes:
!   Mainly useful with spreadsheets or PDFs that were
!   generated with the option to add -# suffixes to make them unique
!   instead of overwriting (but the application doesn't always know
!   what the final filespec was.)
!
!   Note that an existing -# suffix on the fspec$ does not change
!   the logic, i.e., you could start with abc-7.xlsx and end up with
!   abc-7-2.xlsx.
!
!   Note that calling the routine redundantly does not change
!   output from the first time; unless a new file appears in the 
!   meantime (possible race condition concern!)
!
!   WARNING: for Windows, if fspec$ is a native spec (containing a \), logic
!   is limited to use -## suffixes (rather than timestamp) to identify the
!   latest version (starting with limit and working backwards).
!Examples (with possible responses):
!   Fn'FileAgeLatest$("c:\temp\test.pdf") 
!           c:\temp\test-3.pdf
!           c:\temp\test.pdf
!   Fn'FileAgeLates$("dsk4:test.pdf[99,2]")
!           c:\vm\miame\dsk4\099002\test-002.pdf
!   Fn'FileAgeLatest$("test.xlsx")
!           c:\vm\miame\dsk0\007006\test-03.pdf
!---------------------------------------------------------------------
Function Fn'FileAgeLatest$(fspec$ as T_NATIVEPATH:inputonly, &
                limit=20 as b2:inputonly) as T_NATIVEPATH

    map1 locals
        map2 i,i,2
        map2 ext$,s,50
        map2 tmpdir$,s,30
        map2 pline$,s,0
        map2 target$,T_NATIVEPATH
        map2 dir$,T_NATIVEPATH
        map2 ch,b,2
        map2 status,i,4
        map2 stdout$,s,1024     ! enough for a few lines of output
        map2 perms$,s,10
        map2 uid$,s,10
        map2 user$,s,10
        map2 group$,s,10
        map2 size$,s,10
        map2 month$,s,3
        map2 day$,s,3
        map2 time$,s,10
        map2 fields,i,4
        map2 bytes,i,4
        map2 namelen,i,4
        map2 c$,s,1
        map2 dirsep$,s,1
        
    xcall MIAMEX, MX_DIRSEP, dirsep$   
    
    ! split the name from the extension 
    i = .instrr(-1,fspec$,".")
    if i then
        ext$ = fspec$[i,-1]         ! (.ext or maybe .ext[p,pn])
        dir$ = fspec$               ! (save to extract the directory later)
        fspec$ = fspec$[1,i-1]      ! {dir/dev#)name
    endif
    
    ! (note we have to use ASHELL rather than HOSTEX for cross-platform uniformity)
    ! but: that's only good for A-Shell (not native) filespecs!!!!
    if instr(1,fspec$,"/\",INSTRF_ANY) = 0 then   ! fspec$ is DevPPN compatible (not native)
        tmpdir$ = "fnx" + .JOBNAME + ".dir"
        trace.print (99,"fnfileage") tmpdir$, fspec$+"*"+ext$+"/lt"
        xcall AMOS,"dir " + tmpdir$ + "=" + fspec$ + "*" + ext$ + "/lt"  ! dir dirfile={dev:}fspec*.ext{[p,pn])/lt

        ! since output will not show the device or ppn, extract the directory
        ! from the original fspec$
        if dir$ # "" then
            dir$ = Fn'FQFS$(dir$)
            i = .instrr(-1,dir$,"\") 
            if i = 0 then
                i = .instrr(-1,dir$,"/") 
            endif
            if i then
                dir$ = dir$[1,i]
            endif
        endif
        
        i = instr(1,ext$,"[")       ! now chop off any [p,pn]
        if i then
            ext$ = ext$[1,i-1]
        endif
        i = instr(1,fspec$,":")     ! and any dev:
        if i then
            fspec$ = fspec$[i+1,-1]
        endif
        if fspec$[1,1] = "[" then   ! and any [p,pn]file
            i = instr(1,fspec$,"]")
            if i then
                fspec$ = fspec$[i+1,-1]
            endif
        endif
        
        namelen = len(fspec$)
        trace.print (99,"fnfileage") fspec$, dir$, ext$, namelen
        
        ! output looks like this...
        ! 15742 Mar 24 11:42 loads-dstant-41.xlsx
        ! 21460 Mar 24 11:37 loads-dstant-40.xlsx
        ! 17936 Mar 24 11:33 loads-dstant-39.xlsx    

        if lookup(tmpdir$) then
            ch = Fn'NextCh(49234)
            open #ch, tmpdir$, input
            do while eof(ch)=0
                input line #ch, pline$
                trace.print (99,"fnfileage") pline$
                pline$ = strip$(pline$)
                i = instr(20,pline$," ")                ! file starts somewhere after position 20
                if i then
                    target$ = pline$[i+1,-1]            ! target is everything beyond 1st space after pos 20
                    
                    ! make sure the target either matches the initial fspec or has a -# suffix
                    c$ = target$[namelen+1;1]
                    trace.print (99,"emx") target$,c$,namelen,i
                    if c$ = "." or c$ ="-" then
                        target$ = dir$ + target$            ! convert file.ext to dir/file.ext
                        if abs(lookup(target$)) >= 1 then   ! a new file being created will return 0.5
                            .fn = target$                   ! (which we want to ignore)
                            exit
                        endif
                    endif
                endif
            loop
            close #ch
            kill tmpdir$
        endif
        
    elseif dirsep$ = "/" then                                ! for UNIX use hostex ls -lt
        
        fspec$ = Fn'FQFS$(fspec$)       ! (need native spec)
        namelen = len(fspec$)
        xcall HOSTEX, "ls -lt """ + fspec$ + """*" + ext$, status, stdout$
        ! output looks like:
        ! -rwxrwxrwx. 1 root root 4002 Mar 23 11:47 filepec chr(10) ....
        ! (target file starts at 9th token and continues to end of line)
        
        if status = 0 then
            ! loop through the output until we find the first (most recent) valid target
            do while stdout$ # "" and .fn = ""
                i = instr(1,stdout$,chr(10))
                pline$ = stdout$[1,i-1]
                if i then
                    stdout$ = stdout$[i+1,-1]
                else
                    stdout$ = ""
                endif
                
                fields = Fn'ExplodeEx(pline$, " ", FXF_TRIM, &
                    perms$,uid$,user$,group$,size$,month$,day$,time$,target$)
                trace.print (99,"emx") pline$,perms$,uid$,user$,group$,size$,month$,day$,time$
                trace.print (99,"emx") target$, fields
                xcall TRIM, pline$
                if fields >= 9 then
                    i = 0
                    for fields = 1 to 8
                        i = instr(i+1,pline$," ")
                    next fields
                    target$ = pline$[i+1,-1]
                endif
                
                ! make sure the target either matches the initial fspec or has a -# suffix
                c$ = target$[namelen+1;1]
                if c$ = "." or c$ ="-" then
                    xcall SIZE, target$, bytes
                    if bytes > 0 then       ! and has to exist
                        .fn = target$  
                    endif
                endif
                trace.print (99,"emx") c$, namelen, target$, bytes
            loop
        endif
    
    else    ! for Windows, use the more primitive method 
            ! work backwards from -### to 0, stopping at the first one we find
        
        for i = limit to 0 step -1     
            target$ = fspec$ + ifelse$(i,"-"+str(i),"") + ext$
            if lookup(target$) then 
                .fn = target$
                exit
            elseif i > 0 then
                if i < 100 then     ! check for -00# or -0## format
                    target$ = fspec$ + (i using "#ZZ") + ext$
                    if lookup(target$) then 
                        .fn = target$
                        exit
                    elseif i < 10 then  ! check for -0# format
                        target$ = fspec$ + (i using "#Z") + ext$
                        if lookup(target$) then 
                            .fn = target$
                            exit
                        endif
                    endif
                endif
            endif
        next i
        
    endif
    
    if .fn = "" then        ! if we didn't find a suitable target, just use the original
        .fn = fspec$ + ext$ 
    endif
    
EndFunction


++endif
