home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS - Coast to Coast
/
simteldosarchivecoasttocoast.iso
/
pcmag
/
vol10n21.zip
/
PCTODAY.ZIP
/
PCCOPY.BAS
next >
Wrap
BASIC Source File
|
1991-11-01
|
20KB
|
483 lines
'********** PCCOPY.BAS - smart COPY utility that copies only newer files
'Copyright (c) 1991 Ethan Winer
'First published in PC Magazine December 10, 1991
'
'Compile and link as follows:
'
' bc pccopy /o /s;
' link /ex pccopy , , nul, qb.lib today.lib
'
'For best size compile and link using Crescent Software's P.D.Q. like this:
'
' bc pccopy /o /s ;
' link /noe /nod /ex /far /packc pccopy +
' str49152 _noval _noread _noerror , , nul , smalldos [basic7] pdq ;
'---- Function, Subroutine, and TYPE declarations
'
DEFINT A-Z
DECLARE FUNCTION DiskFree& (Drive)
DECLARE FUNCTION DoExclude% (FileName$)
DECLARE FUNCTION FileCount% (Spec$)
DECLARE FUNCTION NameOnly$ (FullName$)
DECLARE FUNCTION PDQShl% (BYVAL Value, BYVAL Bits)
DECLARE FUNCTION PDQShr% (BYVAL Value, BYVAL Bits)
DECLARE FUNCTION Trim$ (Work$)
DECLARE SUB CopyFile (InFile$, OutFile$, DTA AS ANY)
DECLARE SUB ErrorExit (Message$)
DECLARE SUB FileInfo (Info AS ANY, DTA AS ANY)
DECLARE SUB LoadNames (Spec$, Array$())
DECLARE SUB Interrupt (IntNumber, InRegs AS ANY, OutRegs AS ANY)
'DECLARE SUB Interrupt (IntNumber, Regs AS ANY) 'use this with P.D.Q.
TYPE RegType
AX AS INTEGER
BX AS INTEGER
CX AS INTEGER
DX AS INTEGER
BP AS INTEGER
SI AS INTEGER
DI AS INTEGER
Flags AS INTEGER
END TYPE
TYPE DTAType 'this is used by DOS find first/next service
Reserved AS STRING * 21 'reserved for use by DOS
Attribute AS STRING * 1 'the file's attribute
FileTime AS INTEGER 'the file's time
FileDate AS INTEGER 'the file's date
FileSize AS LONG 'the file's size
FileName AS STRING * 13 'the file's name
END TYPE
TYPE FInfo 'translates each file's information
Year AS INTEGER ' into a usable form
Month AS INTEGER
Day AS INTEGER
Hour AS INTEGER
Minute AS INTEGER
Second AS INTEGER
END TYPE
'---- Print the sign-on message first
'
PRINT "PCCOPY 1.00 Copyright (c) 1991 Ethan Winer"
PRINT
'---- Initialize key variables and dimension arrays
'
DIM SHARED Regs AS RegType 'RegType is used by the Interrupt routine
DIM SHARED SourceDTA AS DTAType 'this DTA holds the source file information
DIM TargetDTA AS DTAType 'and this one is for the destination files
DIM File(1 TO 6, 1 TO 2) 'this holds the information for both files
DIM Excluded$(1 TO 10) 'holds the exclude specifications
REDIM SHARED ExcludeList$(0) 'this holds the actual names to trap
DIM SHARED Zero$ 'so everyone can get at it
Zero$ = CHR$(0) 'avoids repeated calls to CHR$() later
DIM SHARED DOS, Temp, NumExclude, TargetSize& 'these save a few bytes of
DOS = &H21 ' code later too
Syntax$ = "Syntax: PCCOPY Source [Destination] [/X Filespec]" + CHR$(13) + CHR$(10) + " -OR- PCCOPY @Responsefile [Destination]"
'---- Parse out the exclude specifications if any were given
'
Cmd$ = UCASE$(COMMAND$) + " " 'work with a copy of COMMAND$ to save code
'the added blank aids parsing below
IF INSTR(Cmd$, "/?") THEN '/? means they want the syntax
CALL ErrorExit(Syntax$)
END IF
DO 'stripping all double blanks now helps too
Temp = INSTR(Cmd$, " ") 'is there a double blank?
IF Temp = 0 THEN EXIT DO 'yes, strip it out and keep looking
Cmd$ = LEFT$(Cmd$, Temp) + MID$(Cmd$, Temp + 2)
LOOP
DO
X = INSTR(Cmd$, "/X") 'see if they used the /X command
IF X = 0 THEN EXIT DO 'no more, all done
Excludes = Excludes + 1 'show we found another one
Temp = INSTR(X + 3, Cmd$, " ") 'find the end of the exclude spec
Excluded$(Excludes) = Trim$(MID$(Cmd$, X + 2, Temp - X - 2)) 'keep spec
Cmd$ = LEFT$(Cmd$, X - 1) + MID$(Cmd$, Temp + 1) 'strip it out
LOOP WHILE Excludes < 10 'up to 10 exclude specifications
'---- Parse the source and destination file arguments
'
Space = INSTR(Cmd$, " ") 'find the space that separates the arguments
IF Space THEN 'if there is a space
Source$ = Trim$(LEFT$(Cmd$, Space)) 'grab the source file spec
Target$ = UCASE$(Trim$(MID$(Cmd$, Space))) 'and the target drive/path
ELSE
Source$ = Trim$(Cmd$) 'otherwise the sole argument is the source
END IF
Help:
IF LEN(Source$) = 0 THEN 'at least a source argument must be given!
CALL ErrorExit(Syntax$)
END IF
IF LEN(Target$) THEN 'if a target was given
Char = ASC(RIGHT$(Target$, 1)) 'see what the rightmost character is
IF Char <> 58 AND Char <> 92 THEN '":" or "\"
Target$ = Target$ + "\" 'add a path\file separator
END IF
END IF
'---- This section of code handles the source specified in a response file
'
IF ASC(Source$) = 64 THEN 'an "@" means read the response file
Regs.DX = VARPTR(SourceDTA) 'show DOS where the new DTA is
Regs.AX = &H1A00 'specify service 1Ah in AH
CALL Interrupt(DOS, Regs, Regs) 'DOS set DTA service
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
Source$ = LTRIM$(MID$(Source$, 2)) 'keep just the file name that follows
OPEN Source$ FOR INPUT AS #1 'open the response file
IF ERR THEN 'if we can't open it, say so and end
CALL ErrorExit("Unable to open file " + Source$)
END IF
WHILE NOT EOF(1)
INPUT #1, ThisFile$ 'read each file name
ThisZ$ = ThisFile$ + Zero$ 'make an ASCIIZ string for DOS
Regs.AX = &H4E00 'find first matching name service
Regs.DX = SADD(ThisZ$) 'show DOS where the file spec is
Regs.CX = 39 'attribute for any type of file
CALL Interrupt(DOS, Regs, Regs) 'now the DTA holds the file date/time
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
PRINT "Copying "; UCASE$(ThisFile$);
IF LEN(Target$) THEN 'show the target only if there is one
PRINT " to "; Target$
ELSE
PRINT
END IF
CALL CopyFile(ThisFile$, Target$ + NameOnly$(ThisFile$), SourceDTA)
FilesCopied = FilesCopied + 1 'track how many we process for later
WEND
Skipped = -1 'force an empty CRLF later
'---- This section of code handles the source files given on the command line
'
ELSE 'copy only the files that need to be
Source$ = Source$ + Zero$ 'make an ASCIIZ string for DOS
FOR X = LEN(Source$) TO 1 STEP -1 'isolate the drive/path if present
Char = ASC(MID$(Source$, X)) 'get the current character
IF Char = 58 OR Char = 92 THEN 'colon or backslash
SourcePath$ = LEFT$(UCASE$(Source$), X) 'keep what precedes the name
EXIT FOR 'bail out of the FOR/NEXT loop
END IF
NEXT
'---- If they used /x create an array holding all the file names to exclude
IF Excludes THEN
FOR X = 1 TO Excludes 'count the number of files to exclude
NumExclude = NumExclude + FileCount%(SourcePath$ + Excluded$(X))
NEXT
REDIM ExcludeList$(1 TO NumExclude) 'create an array to hold their names
FOR X = 1 TO Excludes 'read in all of the names to exclude
CALL LoadNames(SourcePath$ + Excluded$(X), ExcludeList$())
NEXT
END IF
DO 'process all matching source files
DoUpdate = 0 'assume the two files are current
LSET SourceDTA.FileName = "" 'clean out any old name remnants
Regs.DX = VARPTR(SourceDTA) 'show DOS where the source DTA goes
Regs.AX = &H1A00 'specify service 1Ah in AH
CALL Interrupt(DOS, Regs, Regs) 'DOS set DTA service
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
IF FilesRead = 0 THEN 'if this is the first time
Regs.AX = &H4E00 'find first matching name service
Regs.DX = SADD(Source$) 'show DOS where the file spec is
Regs.CX = 39 'attribute for any type of file
ELSE
Regs.AX = &H4F00 'otherwise find next matching file
END IF
CALL Interrupt(DOS, Regs, Regs) 'see if there's a match
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
IF Regs.Flags AND 1 THEN EXIT DO 'if carry flag is set we're done
FilesRead = FilesRead + 1 'otherwise show we read another one
IF DoExclude%(SourceDTA.FileName) GOTO Skip 'exclude this file
CALL FileInfo(File(1, 1), SourceDTA) 'get the source file date and time
Regs.DX = VARPTR(TargetDTA) 'create 2nd DTA for the destination
Regs.AX = &H1A00 'specify service 1Ah in AH
CALL Interrupt(DOS, Regs, Regs) 'DOS set DTA service
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
Dest$ = Target$ + SourceDTA.FileName 'concatenate the target and name
Regs.AX = &H4E00 'find first matching destination
Regs.DX = SADD(Dest$) 'show where the new file spec is
CALL Interrupt(DOS, Regs, Regs) 'see if there's a match
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
IF Regs.Flags AND 1 THEN 'if carry is set the file's not there
DoUpdate = -1 'so we'll have to update it
TargetSize& = 0 'no samed-named target file exists
ELSE
TargetSize& = TargetDTA.FileSize 'this much more is available on dest.
CALL FileInfo(File(1, 2), TargetDTA) 'get the target file date and time
FOR X = 1 TO 6 'compare from year through seconds
IF File(X, 1) > File(X, 2) THEN 'if the target is older
DoUpdate = -1 ' set the flag and exit
EXIT FOR
ELSEIF File(X, 1) < File(X, 2) THEN 'if target is newer there's no
EXIT FOR ' need to continue comparing
END IF
NEXT
END IF
IF DoUpdate THEN 'copy only if necessary
SourceFile$ = SourcePath$ + SourceDTA.FileName
IF Skipped THEN PRINT 'so the name starts on a new line
PRINT "Copying "; SourceFile$;
IF LEN(Target$) THEN 'show target only if there is one
PRINT " to "; Target$
ELSE
PRINT
END IF
CALL CopyFile(SourceFile$, Target$ + SourceDTA.FileName, SourceDTA)
FilesCopied = FilesCopied + 1 'show that we copied another one
Skipped = 0
ELSE
PRINT "."; 'show that a file was just skipped
Skipped = -1
END IF
Skip:
LOOP
END IF
'---- Display the final results and end
'
PRINT 'print a line for clarity
IF Skipped THEN PRINT 'one more if a "." was just printed
IF LEN(ThisZ$) = 0 THEN PRINT FilesRead; "file(s) examined" 'if not @filename
PRINT FilesCopied; "file(s) copied" 'show total files
SUB CopyFile (InFile$, OutFile$, DTAInfo AS DTAType) STATIC
SHARED Regs AS RegType
'-- the next 3 lines strip the trailing CHR$(0) byte; remove with P.D.Q.
IF INSTR(InFile$, Zero$) THEN
InFile$ = LEFT$(InFile$, INSTR(InFile$, Zero$) - 1)
END IF
OPEN InFile$ FOR BINARY AS #2 'open the source file
'-- the next 3 lines strip the trailing CHR$(0) byte; remove with P.D.Q.
IF INSTR(OutFile$, Zero$) THEN
OutFile$ = LEFT$(OutFile$, INSTR(OutFile$, Zero$) - 1)
END IF
ReDo:
OPEN OutFile$ FOR BINARY AS #3 'open/create the destination
IF ERR THEN 'terminate if there's an error
CALL ErrorExit("Unable to open " + OutFile$)
END IF
'-- See how big the source is, and how much room is on the destination
' drive. Prompt for a new disk if not enough room.
Drive = 0 'assume writing to the default drive
IF INSTR(OutFile$, ":") THEN 'no, a drive letter was given
Drive = ASC(OutFile$) - 64 '1=A, 2=B, etc.
END IF
Needed& = LOF(2) 'see how big the source file is
IF Needed& > DiskFree&(Drive) + TargetSize& THEN 'not enough room
CLOSE #3 'close the destination file
KILL OutFile$ 'and erase the zero-byte file we made
PRINT "Insufficient disk space - insert a new disk and press a key, Escape to quit ";
DO
Temp$ = INKEY$
LOOP UNTIL LEN(Temp$)
PRINT
IF Temp$ = CHR$(27) THEN END
GOTO ReDo
END IF
'-- If the target file exists and is bigger, erase it.
IF LOF(3) > Needed& THEN
CLOSE #3 'first close the file
KILL OutFile$ 'then kill it
OPEN OutFile$ FOR BINARY AS #3 'and finally reopen it again
END IF
'-- This is the main file copying portion of the program.
Remaining& = Needed& 'how many bytes remain to be copied
DO
IF Remaining& > 4096 THEN 'copy in 4K blocks
ThisPass = 4096
ELSE
ThisPass = Remaining& 'except the last block may
END IF ' be smaller
IF LEN(Buffer$) <> ThisPass THEN 'make a new buffer only
Buffer$ = SPACE$(ThisPass) ' if necessary
END IF
GET #2, , Buffer$ 'read from Peter
PUT #3, , Buffer$ 'write to Paul
IF ERR THEN 'terminate if an error happens
CALL ErrorExit("Error writing to " + OutFile$)
END IF
Remaining& = Remaining& - ThisPass 'show that we read that much
LOOP WHILE Remaining& 'until there no more remains
CLOSE #2 'close the input file
'-- Set the target date and time to the same as the source file.
Regs.AX = &H5701 'set file date/time service
Regs.BX = FILEATTR(3, 1) 'get the equivalent DOS handle
Regs.DX = DTAInfo.FileDate 'read the source date
Regs.CX = DTAInfo.FileTime 'and the source time
CALL Interrupt(DOS, Regs, Regs) 'call DOS to do it
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
CLOSE #3 'close the target file
END SUB
FUNCTION DiskFree& (Drive) STATIC
Regs.AX = &H3600 'get disk information service
Regs.DX = Drive '0=default, 1=A, 2=B, etc.
CALL Interrupt(DOS, Regs, Regs) 'call DOS
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
BytesPerCluster = Regs.AX * Regs.CX 'AX=sectors/cluster, CX=bytes/sector
FreeClusters& = Regs.BX 'use a long int. for huge partitions
IF Regs.BX < 0 THEN FreeClusters& = Regs.BX + 65536 'adjust for > 32,767
DiskFree& = BytesPerCluster * FreeClusters&
END FUNCTION
FUNCTION DoExclude% (FileName$) STATIC
DoExclude% = 0 'assume we will not exclude this file
Temp = INSTR(FileName$, Zero$) 'find the terminating zero byte
ThisName$ = LEFT$(FileName$, Temp - 1)'trim the name to facilitate matching
FOR X = 1 TO NumExclude 'walk through the excluded name list
IF ThisName$ = ExcludeList$(X) THEN 'we found a match
DoExclude% = -1 'show that this is to be excluded
EXIT FOR 'and skip the remaining names
END IF
NEXT
END FUNCTION
SUB ErrorExit (Message$) STATIC
PRINT Message$
END
END SUB
FUNCTION FileCount% (Spec$) STATIC
SpecZ$ = Spec$ + Zero$ 'make a local ASCIIZ copy
Regs.DX = VARPTR(SourceDTA) 'set the new DTA address
Regs.AX = &H1A00 'specify service 1Ah, set DTA service
CALL Interrupt(DOS, Regs, Regs) 'call DOS to do the real work
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
Regs.DX = SADD(SpecZ$) 'the file specification address
Regs.CX = 39 'find files, also hidden/read-only
Regs.AX = &H4E00 'find first matching name
Count = 0 'clear the counter
DO
CALL Interrupt(DOS, Regs, Regs) 'see if there's a match
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
IF Regs.Flags AND 1 THEN EXIT DO 'no more files
Count = Count + 1 'we found another file
Regs.AX = &H4F00 'find the next matching name
LOOP
FileCount% = Count 'assign the function
END FUNCTION
SUB FileInfo (Info AS FInfo, DTA AS DTAType) STATIC
Info.Year = PDQShr%(DTA.FileDate AND &HFE00, 9) + 80 'compute the year
Info.Month = PDQShr%(DTA.FileDate AND &H1E0, 5) 'compute the month
Info.Day = DTA.FileDate AND &H1F 'compute the day
Info.Hour = PDQShr%(DTA.FileTime AND &HF800, 11) 'compute the hour
Info.Minute = PDQShr%(DTA.FileTime AND &H7E0, 5) 'compute the minute
Info.Second = PDQShl%(DTA.FileTime AND &H1F, 1) 'compute the second
END SUB
SUB LoadNames (Spec$, Array$()) STATIC
SpecZ$ = Spec$ + Zero$ 'make a local ASCIIZ copy
'---- The following code isn't really needed because we know that FileCount%
' has already set the DTA address. It is shown here merely for clarity.
'Regs.DX = VARPTR(SourceDTA) 'assign the new DTA address
'Regs.AX = &H1A00 'specify service 1Ah
'CALL Interrupt(DOS, Regs, Regs) 'DOS set DTA service
Regs.DX = SADD(SpecZ$) 'the file spec address
Regs.CX = 39 'find files, also hidden/read-only
Regs.AX = &H4E00 'find first matching name
DO
CALL Interrupt(DOS, Regs, Regs) 'see if there's a match
'CALL Interrupt(DOS, Regs) 'use this with P.D.Q.
IF Regs.Flags AND 1 THEN EXIT DO 'no more
ThisEl = ThisEl + 1 'assign the current element
Zero = INSTR(SourceDTA.FileName, Zero$) 'find zero and assign name
Array$(ThisEl) = LEFT$(SourceDTA.FileName, Zero - 1) 'assign the name
Regs.AX = &H4F00 'find next matching name service
LOOP
END SUB
FUNCTION NameOnly$ (FullName$) STATIC 'extracts file name from a full name
NameOnly$ = FullName$ 'assume there's no drive or path
FOR X = LEN(FullName$) TO 1 STEP -1 'walk through the name backwards
Temp = ASC(MID$(FullName$, X)) 'look at this character
IF Temp = 58 OR Temp = 92 THEN 'colon or backslash?
NameOnly$ = MID$(FullName$, X + 1) 'yes, keep just what follows
EXIT FOR 'and we're all done
END IF
NEXT
END FUNCTION
FUNCTION Trim$ (Work$) STATIC
Trim$ = LTRIM$(RTRIM$(Work$)) 'strip both leading and trailing blanks
END FUNCTION