
;   FILENAME: IFINDFIL.ASM
;
;   Copyright (c) 1988, 1992 by Borland International, Inc.
;
;   DESCRIPTION:  This module implement a routine that performs a recursive
;   search through a hard disk of a specified file.
;
;   ASSEMBLY INSTRUCTIONS: To assemble this module use the following
;   TASM command line.
;
;       TASM /dMDL=memorymodel ifindfil
;
;   'memorymodel' in the above command line may be replaced by TINY, SMALL,
;   MEDIUM, COMPACT, LARGE or HUGE. If assembling this module to run on
;   a 286/386 machine, turn on the P286 directive in order to take advantage of
;   286/386 specific instructions. For example:
;
;       TASM /dMDL=memorymodel /jP286 ifindfil

jumps

%tabsize 4

ifndef  MDL
    display "Error: This module requires that you provide a memory model"
    display "    definition on the command line. I.E. /dMDL=SMALL."
    err ; Force a fatal error
else
    ideal                   ; Use TASM's Ideal mode
%   model   MDL,pascal      ; Define the memory model

    include "iwhglobl.inc"
    include "imacros.mac"
    include "idos.inc"
    include "dos.inc"
    include "idos.mac"
    include "kbd.inc"
    include "bios.inc"
    include "ibios.mac"

    codeseg
        global      BreakPressed:byte  ; Check this often for a nonzero
                                       ; value. Terminate if a nonzero
                                       ; value is found.

    dataseg
        AllFiles    db  "*.*",0     ; ASCIIZ string representing all files
        SwitchChar  db  1,'\'       ; Directory seperating character
        CurrentDir  db  MAX_PATH_LENGTH dup (0)
        MakePascalString BlankLine, <13, 10>

        ; The following variable keep track of going through the
        ; file specifications that were given on the command line.
        FileSpecIndex db ?
        FileSpecPtr   dw ?


    codeseg

    macro   NewLine                 ; Force a new line
;;        ifdef  _286_
;;            push    seg BlankLine
;;            push    offset BlankLine
;;        else
;;            mov     ax, seg BlankLine
;;            push    ax
;;            mov     ax, offset BlankLine
;;            push    ax
;;        endif
        call    WritePascalString,seg BlankLine,offset BlankLine
    endm

TerminateKeyMssg db 13,10
                 db '**ERROR** Whereis terminated due to keypress.'
                 db 13,10,0

    ; The following messages should not include a line feed.
PauseKeyMssg      db 'Press space to continue....',13,0
PauseKeyEraseMssg db '                           ',13,0

    proc    FindFiles

    ;   This routine does a recursive search down through the directory
    ;   structure of a disk looking for a file. If the routine finds a
    ;   match it displays the complete drive\path\filename. The routine
    ;   uses the following algorithm:
    ;
    ;       make room for a local copy of the Dta
    ;       change directories to the location of the file we're looking for
    ;       find all matches in the current directory
    ;       for each sub-directory in the current directory
    ;           Do a recursive call to this routine
    ;       restore the original directory
    ;
    ;   The routine uses the global variable FileSpec as the specification
    ;   of the file to search for.
    ;   If any key is pressed, a message is printed and then this routine
    ;   calls Terminate.
    ;
    ;   Input
    ;       DS - points to the segment in which FileSpec resides
    ;       Path - A pascal style string representing the path in which to
    ;              search for the file
    ;   Output
    ;       none
    ;   Calling convention
    ;       Pascal
    ;   Registers modified
    ;       ax, bx, cx, dx, si, es, flags

    arg     Path:byte:MAX_PATH_LENGTH=PARM_SIZE
    local   DataTransferArea:byte:DTA_SIZE=LOCAL_SIZE ;

    ; The PASCAL calling convention on the MODEL statement causes TASM
    ; to do the following automatically.
;        push    bp
;        mov     bp, sp
;        ; make room for the Dta, Drive, Path and Filename on the stack
;        sub     sp, LOCAL_SIZE

        mov     [FileSpecIndex],1       ; Keep track which one we are searching
        mov     [FileSpecPtr],offset FileSpec


        mov     si, bp                  ; Get the address of the Dta buffer
        sub     si, DTA_SIZE
        push    ds                      ; Store ds before call to SetDTA
        SetDTA  <ss>, <si>              ; Set the current Dta address
        pop     ds                      ; Restore ds after SetDTA
        if @CodeSize eq 0               ; FindFiles is near
            add     si, DTA_SIZE + 5    ; Get the address of the Path
        else
            add     si, DTA_SIZE + 7    ; Get the address of the Path
        endif

        xor     bx, bx
        mov     bl, [byte ss:si-1]      ; Get the length byte
        cmp     bl, 1                   ; Check if the path is 1 letter. If it
        jle     ChangeDirs              ; is we don't want to remove it.
        cmp     [byte ss:si+bx-1], '\'  ; Check if the path ends with a '\'. If
        jne     ChangeDirs              ; it does, remove it.
        mov     [byte ss:si+bx-1], 0
        dec     [byte ss:si-1]
    ChangeDirs:
        push    ds
        ChangeDirectory <ss>, <si>      ; Change the directory
        GetCurrentDir   <0>, <seg CurrentDir>, <offset CurrentDir>
        pop     ds
        if (@Cpu and 100b) eq 100b
            push    seg CurrentDir
            push    offset CurrentDir
        else
            mov     ax, seg CurrentDir
            push    ax
            mov     ax, offset CurrentDir
            push    ax
        endif
        mov     ax, 0020h               ; Replace spaces with 0
        mov     cx, MAX_PATH_LENGTH
        call    FindAndReplace

    FirstFile:
        mov     ax,[FileSpecPtr]
        inc     ax                      ; Get past length byte
        FindFirst   <0FFh>, <ds>, <ax>
    NextFile:                           ; Find each of the matching files
        cmp     ax, 0
        jne     CheckNextFileSpec

        ; Check if the user has pressed a key to interrupt WHEREIS.
        GetKbdStatus
        jz      NoKey

        call    KeyWasPressed


        ; Come here to print a matching file.
    NoKey:
        if (@Cpu and 100b) eq 100b
            push    seg Drive           ; Get the address of the drive
            push    offset Drive        ; description
        else
            mov     ax, seg Drive
            push    ax
            mov     ax, offset Drive
            push    ax
        endif
        mov     al, [Drive]
        xor     ah, ah
        call    WritePascalString       ; Display the drive description
        mov     ax, seg CurrentDir
        mov     es, ax
        mov     di, offset CurrentDir
        cmp     [byte es:di], 'A'       ; Check if the string is empty
        jl      DontNeedBackSlash
        dec     di                      ; Decrement the offset of the pointer
    DontNeedBackSlash:
        push    es
        push    di
        call    WriteASCIIZString
        if (@Cpu and 100b) eq 100b
            push    seg SwitchChar
            push    offset SwitchChar
        else
            mov     ax, seg SwitchChar
            push    ax
            mov     ax, offset SwitchChar
            push    ax
        endif
        call    WritePascalString

        mov     si, bp
        sub     si, DTA_SIZE - (offset (Dta).Filename)
        push    ss  si  ; Push an extra copy of offset of filename

        ; Write the filename. Pass the address of the filename
        call    WriteASCIIZString,ss,si
        NewLine

        pop     si ax  ; Offset of filename   ax:si
        mov     di,offset CurrentDir
        push    ds                      ; Store ds before call to
                                        ; ExecuteDosCommand and SetDTA
        call    ExecuteDosCommand       ; Do the command for the file.

        mov     si, bp                  ; Get the address of the Dta buffer
        sub     si, DTA_SIZE
        SetDTA  <ss>, <si>              ; Set the current Dta address
        pop     ds                      ; Restore ds after SetDTA

        FindNext
        jmp     NextFile

    CheckNextFileSpec:
        cmp     [BreakPressed],0        ; Check for CTRL-BREAK
        jnz     GiveAbortMessage

        add     [FileSpecPtr],FILE_SPEC_SIZE    ; Point to next filespec
        mov     al,[FileSpecIndex]              ; Bump up counter
        inc     al
        mov     [FileSpecIndex],al
        cmp     al,[FileSpecCounter]
        jle     FirstFile                       ; Go back to search another filespec

    CheckDirectories:
        push    ds
        FindFirst   <010000b>, <(seg AllFiles)>, <(offset AllFiles)>
        pop     ds
    NextDirectory:
        cmp     ax, 0                   ; Check if we've found a sub-directory
        je      CheckAttributes
        jmp     Exit
    CheckAttributes:                    ; Check if it's a directory
        mov     si, bp                  ; Get the address of the attribute
        sub     si, DTA_SIZE - (offset (Dta).FileAttribute)
        mov     al, [byte ss:si]        ; Get the directory entries attributes
        and     al, 10000b
        cmp     al, 10000b
        jne     GetNextDirectory

        ; Check if the directory is '.' or '..'

        mov     si, bp                  ; Get the address of the attribute
        sub     si, DTA_SIZE - (offset (Dta).Filename)
        cmp     [byte ss:si], '.'       ; If it's '.' or '..' then skip
        je      GetNextDirectory        ; it

        ; Copy the new path onto the stack

        sub     sp, MAX_PATH_LENGTH     ; Make room on the stack
        mov     bx, sp
        push    ss                      ; Push segment address of Path
        mov     si, bp
        if @CodeSize eq 0               ; FindFiles is near
            add     si, 4               ; get offset of current path string
        else
            add     si, 6
        endif
        push    si                      ; Push offset of path
        push    ss                      ; Push address to copy to
        push    bx
        mov     al, [byte Path]         ; Get the path length
        inc     al                      ; Copy the length byte also
        call    ByteCopy                ; Copy the path onto the stack

        ; Append the new directory to the path on the stack

        push    es di                   ; Save es:di before call to
                                        ; GetASCIIZStrLen
        mov     si, bp                  ; Get the address of the current Dta

        ; Get the address of the directory name from it's location in the Dta

        sub     si, DTA_SIZE - (offset (Dta).Filename)
        call    GetASCIIZStrLen,ss,si   ; Get the length of the directory name
        pop     di es                   ; Restore es:di

        cmp     [byte es:di-1], '\'     ; Check if the path on the stack ends
        je      HasBackSlash            ; with a '\'. If not append one.
        mov     [byte es:di], '\'
        inc     di
        push    si
        mov     si, sp                  ; Adjust the length byte of the string
        inc     [byte ss:si+2]
        pop     si
    HasBackSlash:
        ; Copy the directory name
        call    ByteCopy,ss,si, \       ; Address of the directory name.
                         es,di          ; Address to copy directory name to

        mov     si, sp                  ; Adjust the length byte of the string
        dec     al                      ; we appended to. Don't include the
        add     [byte ss:si], al        ; terminating 0 in the length

        ; Do recursive call

        call    FindFiles               ; Do the search for the file(s)
        push    ds
        mov     ax, bp
        if  @CodeSize eq 0              ; Near code models
            add     ax, 5
        else
            add     ax, 7
        endif
        ChangeDirectory <ss>, <ax>      ; Change to the directory that was
        pop     ds                      ; active before the recursive call

;; Remember, the following cleanup is done automatically by Pascal Model.
;;        add     sp, MAX_PATH_LENGTH     ; Remove space allocated on the stack

        mov     si, bp
        sub     si, DTA_SIZE
        push    ds
        SetDTA  <ss>, <si>              ; Restore the Dta
        pop     ds
    GetNextDirectory:
        FindNext                        ; Find the next sub-directory
        jmp     NextDirectory
    Exit:

     ; Once again, the cleanup is done automatically because of PASCAL model.
;        add     sp, LOCAL_SIZE
;        pop     bp
        ret
    endp    FindFiles


    proc    KeyWasPressed

    ;   This routine handles pausing if a space or ^S was pressed,
    ;   or terminating WHEREIS if any other key was pressed.

        ; Throw out the key(s) that were pressed.
    DiscardKeys:
        GetChar
        cmp     al,' '          ; Check for space pressed to pause
        je      JustPausing
        cmp     al,'s'-'a'+1    ; Check for ^S pressed to pause
        je      JustPausing

    FlushKeyboard:
        GetKbdStatus
        jz   GiveAbortMessage
        GetChar
        jmp  FlushKeyboard


    GiveAbortMessage:
        ; Give a message to the user that WHEREIS is terminating.
        call    WriteASCIIZString,seg TerminateKeyMssg,offset TerminateKeyMssg

        call    Terminate


    JustPausing:
        ; Give a message to the user that WHEREIS is pausing
        call    WriteASCIIZString,seg PauseKeyMssg,offset PauseKeyMssg

    WaitForSpace:
        GetKbdStatus
        jz   WaitForSpace
        GetChar
        cmp  al,' '
        jne  FlushKeyboard

        ; Now get rid of the reminder
        call    WriteASCIIZString,seg PauseKeyEraseMssg,offset PauseKeyEraseMssg

        ret
    endp    KeyWasPressed


endif   ; ifndef MDL

end
