Use this freely, but I would appreciate it if you would give me credit for whatever programs you create that
make use of these routines.
I started this yesterday morning. This morning, I got to work on optimizing it for speed, and here it is!
Hope you enjoy it!
-Jesse
P.S. The ReadChunk and WriteChunk SUBs and the IsItOdd% FUNCTION are the only necessarey routines. If you can
halfway figure out how the SUBs work, you'll be able to use these routines without the rest of the program.
========================================================
'QCOPY
'COPY replacement for QB/VBDOS/PDS, probably PB, likely QBasic
'by Jesse Dorland
'Started 6/1/1996
'Revised, Optimized for speed 6/2/1996
'Public Domain - Use freely
'For a speed comparison (QCOPY vs. COPY) e-mail me at
' merl...@primenet.com
'Please put my name (Jesse Dorland) somewhere in the subject line.
'Here it is, my replacement for the DOS COPY command. One wonders
'what the guys at Microsoft were high on when they wrote COPY in their
'all-powerful assemblers, because 90% of the time, QCOPY is *faster* than
'COPY! I no longer have to SHELL to DOS's COPY command, because now I
'actually have a better alternative, and if you're reading this, so do you!
'What follows is a brief description of how QCOPY works and of some of the
'routines used.
'In a nutshell, QCOPY works by grabbing a chunk of data from the source
'file, and dumping it to the destination file. This is what makes it
'so fast. The data is grabbed using GET, and written using PUT. The data
'that has been read is stored in the string variable STORAGE. This string
'has a fixed length of 32k (32,767!) characters. Without a fixed length,
'only 23,000 characters could have been used, hampering performance.
'The 32,767 characters are read from the file in one GET statement. This
'is very speedy. Then, immediately after, the data is dumped to the
'destination file. That means that data is copied in 32k chunks - that's
'FAST.
'The StartQCOPY routine sets up QCOPY and prompts the user for filenames
'if they have not been entered at the command line. It then passes
'control to CopyFile, which handles the actual copying.
'The CopyFile routine prepares the program to copy the file. While the
'actual copying is done in ReadChunk and WriteChunk, CopyFile calls these
'two subroutines until the file has been copied.
'ReadChunk grabs a 32k chunk of data from the source file and stores it in
'the fixed string variable STORAGE. If there is less then 32k left to
'be copied, it has its own alternate reading and writing routine built-in.
'WriteChunk dumps the 32k chunk of data to the destination file, if the
'alternate routine in ReadChunk has not already been used. It also updates
'the length of the destination file and prints how far along (percent-wise)
'the copying is.
'CheckCommand checks to see if the user has entered filenames at the command
'line. If the user has, and the filenames exist, the filename prompting
'is skipped. You will still be prompted, however, if the destination file
'already exists.
'The IsItOdd% FUNCTION figures out if a number contained in a variable is
'even or odd. It can be tinkered with, but right now it only supports
'long (&) variables, since the variable that stores the remaining bytes to
'be read is a long variable. This function is required for the ReadChunk
'subroutine
'There you have it. Feel free to change this program to your little heart's
'content. Just don't slap another title on it and give it away. You can
'use the *routines* in your programs, but don't plagiarize my work and use
'the exact same code with your name on it.
'You can get a speed comparison by e-mailing me. My address is at the
'beginning of this program.
'To get this to work with QBasic you'll have to change all appearances of the
'number 32767 to a lower number, probably below 23000. That should make
'it work, but it will be much slower.
DECLARE SUB StartQCOPY () 'This SUB starts QCOPY
DECLARE SUB ReadChunk () 'This SUB reads a 32k chunk
DECLARE SUB WriteChunk () 'This SUB writes a 32k chunk
DECLARE SUB CheckCommand () 'This SUB checks command line parameters
DECLARE SUB CopyFile () 'This starts the copying procedure
DECLARE FUNCTION IsItOdd% (VAR&) 'This FUNCTION determines whether a number
'is even or odd
DIM SHARED PARAMS$(1 TO 2)
DEFINT A-Z
'$DYNAMIC
DIM SHARED STORAGE(1) AS STRING * 32767
'OPEN "DOOM.BAK" FOR BINARY AS #1
'PRINT LOF(1)
'END
CheckCommand
StartQCOPY
SUB CheckCommand
'Print the opening message
CLS
PRINT "QCOPY v1.0"
PRINT "by Jesse Dorland - 6/1/1996"
PRINT "Use freely"
PRINT
PRINT
'Gain access to all variables
SHARED SRCFILE$, DSTFILE$, LENGTH&, LENGTH2&, T#
'Set the parser to check the first word
PCHECK% = 1
'Get the command line parameters
COMM$ = COMMAND$
'Get the first two parameters entered and store them
FOR I% = 1 TO LEN(COMM$)
IF ASC(MID$(COMM$, I%, 1)) = 32 THEN GOSUB SWITCH: GOTO SKIP
PARAMS$(PCHECK%) = PARAMS$(PCHECK%) + MID$(COMM$, I%, 1)
SKIP:
NEXT I%
DONESTORE:
FOR I% = 1 TO 2
PARAMS$(I%) = LTRIM$(PARAMS$(I%)): PARAMS$(I%) = RTRIM$(PARAMS$(I%))
NEXT I%
IF PARAMS$(1) = "" OR PARAMS$(2) = "" THEN EXIT SUB
'Check to see if the source file exists
OPEN PARAMS$(1) FOR BINARY AS #1
IF LOF(1) = 0 THEN CLOSE : EXIT SUB
CLOSE
'Check to see if the source and destination files are the same
IF PARAMS$(1) = PARAMS$(2) THEN EXIT SUB
'Start the copying.
OPEN PARAMS$(2) FOR BINARY AS #1
SRCFILE$ = PARAMS$(1)
DSTFILE$ = PARAMS$(2)
LOCATE 6, 1: PRINT "Source file: "; SRCFILE$
LOCATE 7, 1: PRINT "Destination file: "; DSTFILE$
IF LOF(1) > 0 THEN
PRINT "Destination file already exists! Overwrite? [Y/N]";
LOCATE , , 1
GETKEY:
WHILE A$ = ""
A$ = INKEY$
WEND
A$ = UCASE$(A$)
IF A$ <> "Y" AND A$ <> "N" THEN GOTO GETKEY
IF A$ = "Y" THEN LOCATE 8, 1: PRINT STRING$(80, " "): CLOSE : KILL DSTFILE$: CopyFile
IF A$ = "N" THEN CLOSE : StartQCOPY
END IF
CLOSE
CopyFile
END
EXIT SUB
SWITCH:
IF PCHECK% = 2 THEN GOTO DONESTORE ELSE
IF PCHECK% = 1 THEN PCHECK% = 2
RETURN
END SUB
SUB CopyFile
'Gain access to all variables
SHARED SRCFILE$, DSTFILE$, LENGTH&, LENGTH2&, T#, ALTSTORE%
'Open the source file as read only to prevent any "accidents"...
OPEN SRCFILE$ FOR BINARY ACCESS READ AS #1
'Open the destination file
OPEN DSTFILE$ FOR BINARY AS #2
'Get the length in bytes of the source file
LENGTH& = LOF(1)
LOCATE 9, 1: PRINT "Size:"; LENGTH&; "bytes"
'Turn the timer on so we can time the copying
TIMER ON
'Store the current timer value in T#
T# = TIMER
'Start a DO..LOOP loop that only quits when it reaches the end of the
'source file.
DO UNTIL (EOF(1))
'Read a 32k chunk from the source file
ReadChunk
'Dump the chunk to the destination file
WriteChunk
'As a precaution, check to make sure that the destination file hasn't grown
'to a larger size then the source file.
IF LENGTH2& >= LENGTH& THEN GOTO DONE
LOOP
DONE:
'Update the T# variable to contain the number of seconds copying took.
T# = TIMER - T#
'Print the time
PRINT USING "Time: ###.## seconds"; T#
'Close all files
CLOSE
'End the program
END
END SUB
DEFSNG A-Z
FUNCTION IsItOdd% (VAR&)
'Turn VAR& into a string
VAR$ = STR$(VAR&)
'Get the last character in the string
VAR$ = RIGHT$(VAR$, 1)
'Convert the last character to a single number
VAR = VAL(VAR$)
SELECT CASE VAR
'IsItOdd% = 0 if even, 1 if odd
CASE IS = 0
IsItOdd% = 0
CASE IS = 1
IsItOdd% = 1
CASE IS = 2
IsItOdd% = 0
CASE IS = 3
IsItOdd% = 1
CASE IS = 4
IsItOdd% = 0
CASE IS = 5
IsItOdd% = 1
CASE IS = 6
IsItOdd% = 0
CASE IS = 7
IsItOdd% = 1
CASE IS = 8
IsItOdd% = 0
CASE IS = 9
IsItOdd% = 1
END SELECT
END FUNCTION
DEFINT A-Z
SUB ReadChunk
'Gain access to all variables
SHARED SRCFILE$, DSTFILE$, LENGTH&, LENGTH2&, T#, ALTSTORE%
'Clear 23000 bytes of string space to store the file's data
STORAGE(0) = STRING$(32767, " ")
'Check to see if there are less then 32,767 bytes left to read in the file,
'and if there are, change the amount of free string space to avoid making
'the destination file bigger than the source file.
'Read the 32k chunk
IF LENGTH& - LENGTH2& < 32767 THEN
REST& = LENGTH& - LENGTH2&
B$ = STRING$(REST& \ 2, " "): C$ = STRING$(REST& \ 2, " ")
ODDEVEN% = IsItOdd%(REST&)
IF ODDEVEN% = 1 THEN C$ = STRING$((REST& \ 2) - 1, " ")
GET #1, , B$: GET #1, , C$
PUT #2, , B$: PUT #2, , C$
ALTSTORE% = 1
GOTO SKIPGET
END IF
GET #1, , STORAGE(0)
SKIPGET:
END SUB
SUB StartQCOPY
'Gain access to all variables used in program
SHARED SRCFILE$, DSTFILE$, LENGTH&, LENGTH2&, T#
'Set the screen to text mode and clear the screen
SCREEN 0
CLS
'Print the title
PRINT "QCOPY v1.0"
PRINT "by Jesse Dorland - 6/1/1996"
PRINT "Use freely"
PRINT
PRINT
INPUTSOURCE:
'Ask the user for the source file
INPUT "Source File: ", SRCFILE$
'Make sure the user entered something (instead of just pressing ENTER)
IF SRCFILE$ = "" THEN LOCATE 5, 1: PRINT STRING$(80, " "): LOCATE 5, 1: PRINT "No source file specified!": GOTO
INPUTSOURCE:
'Open the file
OPEN SRCFILE$ FOR BINARY AS #1
'If LOF(1) variable equals 0, then the file did not exist before it was
'opened and it cannot be used
IF LOF(1) = 0 THEN
'Print an error message and make the user start over
LOCATE 5, 1
PRINT STRING$(80, " "): LOCATE 5, 1: PRINT "File does not exist!"
LOCATE 6, 1: PRINT STRING$(80, " "): LOCATE 6, 1
CLOSE
KILL SRCFILE$
GOTO INPUTSOURCE:
'Close the file and go ahead if the file already existed
ELSEIF LOF(1) > 0 THEN
CLOSE
END IF
INPUTDEST:
'Ask the user for the destination file
INPUT "Destination File: ", DSTFILE$
'Check to see if the source and destination files ar ethe same
IF SRCFILE$ = DSTFILE$ THEN
'Print an error message and return the the destination input
LOCATE 5, 1
PRINT STRING$(80, " "): LOCATE 5, 1
PRINT "Source and destination files are the same!"
LOCATE 6, 1: PRINT STRING$(80, " "): PRINT STRING$(80, " "): LOCATE 6, 1
GOTO INPUTSOURCE:
'Check to see if a filename was entered
ELSEIF DSTFILE$ = "" THEN
'If not, print an error and make the user go back
LOCATE 5, 1: PRINT STRING$(80, " "): LOCATE 5, 1
PRINT "No destination file specified!"
LOCATE 7, 1: PRINT STRING$(80, " "): LOCATE 7, 1: GOTO INPUTDEST
END IF
'Open the file to see if it exists
OPEN DSTFILE$ FOR BINARY AS #1
'If the file exists...
IF LOF(1) > 0 THEN
'Ask the user if they really want to overwrite the file
LOCATE 8, 1: PRINT STRING$(80, " "): LOCATE 8, 1
PRINT "Destination file already exists! Overwrite? [Y/N] ";
LOCATE , , 1
WAITKEY:
WHILE A$ = ""
A$ = INKEY$
WEND
A$ = UCASE$(A$)
IF A$ <> "Y" AND A$ <> "N" THEN GOTO WAITKEY
IF A$ = "Y" THEN GOTO Copy
IF A$ = "N" THEN
LOCATE 8, 1: PRINT STRING$(80, " "): LOCATE 7, 1: PRINT STRING$(80, " ")
LOCATE 7, 1: CLOSE : GOTO INPUTDEST
END IF
END IF
Copy:
CLOSE
'If the user wanted to overwrite a file, it must be deleted first to make
'sure that garbage from the original file is not left over.
KILL DSTFILE$
'Start the copying process
CopyFile
END SUB
SUB WriteChunk
'Gain access to all variables
SHARED SRCFILE$, DSTFILE$, LENGTH&, LENGTH2&, T#, ALTSTORE%
'Dump the 32k chunk to the destination file
IF ALTSTORE% = 1 THEN GOTO SKIPPUT
PUT #2, , STORAGE(0)
SKIPPUT:
'Get the new length of the destination file.
LENGTH2& = LOF(2)
'Print how much of the copying is complete based on the length of the
'source file compared to the destination file.
LOCATE 10, 1, 0: PRINT USING "###% complete"; LENGTH2& / (LENGTH& / 100)
END SUB
--
====================================
Very funny Scotty!
Now beam down my clothes!
Capt. James T. Kirk
====================================
-Jesse
>'has a fixed length of 32k (32,767!) characters. Without a fixed length,
>'only 23,000 characters could have been used, hampering performance.
Why? Using a string length not evenly divisible by 512 makes for
inefficient code. The above is 63.9980 / 512 byte sectors.
>'ReadChunk grabs a 32k chunk of data from the source file and stores it in
>'the fixed string variable STORAGE. If there is less then 32k left to
>'be copied, it has its own alternate reading and writing routine built-in.
Again, why does it need a fixed length string? A dynamic string works
much better...
>'WriteChunk dumps the 32k chunk of data to the destination file, if the
>'alternate routine in ReadChunk has not already been used. It also updates
>'the length of the destination file and prints how far along (percent-wise)
>'the copying is.
What you've hit upon is a very common algorithm for moving big chunks
of data. I've used the same basic idea in Asm, BASIC, C and PASCAL. I
first saw it about 10 years ago - somewhere...I forget. At any rate,
you can boil the main part of the routine down to about 8 lines in
BASIC...
I use something like this in PowerBasic all the time...PB's maximum
string size is 32750, so I just round it back to the next number
evenly divisible by 512...
FLen& = LOF(InComing)
'Check for available string space...
DO
IF FLen& > 32256 THEN BSize% = 32256 ELSE BSize% = FLen&
Buffer$ = SPACE$(BSize%)
GET #File1, , Buffer$
PUT #File2, , Buffer$
FLen& = FLen& - BSize%
LOOP WHILE FLen& <> 0
32256 = (63 / 512 byte sectors) keeping it even for when BASIC
calls DOS to do the reads and writes...
JE McTaggart
t...@iguana.ruralnet.net
La Junta, Colorado
You can just evaluate the odd or evenness of the value by looking at its
bitmap. Even when you convert from a string, if it's a numeric string, you
can do a bit evaluation on the last character in the string: i.e., "123". You
know the number is odd by looking at the "3" and seeing which bits are turned
on. You can even do this for letters: "ABC". If A,C,E,etc. are "odd", you
can determine this because the collating sequence for digits and letters
allows it (in the ASCII set).
Your enthusiasm is much appreciated, but by the length of your program, even
allowing for the generous (and much-to-be applauded) comments you included, I
suspect other areas of your code could be optimized as well.
I guess this is a bit unfair of me to take a pot-shot like this, since I
didn't look at the code in detail (and I apologize for that), but I encourage
you to look more closely at it yourself to see if you can optimize it.
--
++ ++ "Well Samwise: What do you think of the elves now?"
||\ /|| --fbag...@mid.earth.com
|| v ||ichael Martinez (mma...@basis.com)
++ ++------------------------------------------------------
> >'ReadChunk grabs a 32k chunk of data from the source file and stores it in
> >'the fixed string variable STORAGE. If there is less then 32k left to
> >'be copied, it has its own alternate reading and writing routine built-in.
>
> Again, why does it need a fixed length string? A dynamic string works
> much better...
What I meant by this fixed string variable is the string array STORAGE which
has a fixed length of 32,767 bytes. Again, out of string space errors occurred
if I did not make it a fixed length string.
>
> >'WriteChunk dumps the 32k chunk of data to the destination file, if the
> >'alternate routine in ReadChunk has not already been used. It also updates
> >'the length of the destination file and prints how far along (percent-wise)
> >'the copying is.
>
> What you've hit upon is a very common algorithm for moving big chunks
> of data. I've used the same basic idea in Asm, BASIC, C and PASCAL. I
> first saw it about 10 years ago - somewhere...I forget. At any rate,
> you can boil the main part of the routine down to about 8 lines in
> BASIC...
>
As I said in the first post, the actual code is very small. I created
the whole program to demonstrate how something like that might be used.
I am aware that the algorithm is not that hard to figure out; however,
I have seen many posts asking for a DOS COPY replacement. Obviously
if they had known about this algorithm, they would have written
themselves a program and not bothered to ask the question. I just
happened to tinker around with this thing and though it might be
useful to others.
> I use something like this in PowerBasic all the time...PB's maximum
Good for you. :-)
> string size is 32750, so I just round it back to the next number
> evenly divisible by 512...
>
> FLen& = LOF(InComing)
> 'Check for available string space...
>
> DO
> IF FLen& > 32256 THEN BSize% = 32256 ELSE BSize% = FLen&
>
> Buffer$ = SPACE$(BSize%)
> GET #File1, , Buffer$
> PUT #File2, , Buffer$
> FLen& = FLen& - BSize%
> LOOP WHILE FLen& <> 0
>
> 32256 = (63 / 512 byte sectors) keeping it even for when BASIC
> calls DOS to do the reads and writes...
>
The main reason I posted this program is for other people who were
not aware of this simple little program. I comment several times
throughout the program that it is a simple procedure. I was not
trying to say that it was any sort of new discovery. Anyone else
who sat down and thought about it could have come up with the
same thing as I did; I just saved them the trouble of thinking! ;-)
> JE McTaggart
> t...@iguana.ruralnet.net
> La Junta, Colorado
-Jesse
> You can just evaluate the odd or evenness of the value by looking at its
> bitmap. Even when you convert from a string, if it's a numeric string, you
> can do a bit evaluation on the last character in the string: i.e., "123". You
> know the number is odd by looking at the "3" and seeing which bits are turned
> on. You can even do this for letters: "ABC". If A,C,E,etc. are "odd", you
> can determine this because the collating sequence for digits and letters
> allows it (in the ASCII set).
>
> Your enthusiasm is much appreciated, but by the length of your program, even
> allowing for the generous (and much-to-be applauded) comments you included, I
> suspect other areas of your code could be optimized as well.
>
Absolutely right. I also mentioned somewhere that the actual copying
code was very simple; if you do it right, it can be done in 10 lines
of code. I made an entire utility to show what you can do with this
simple routine. I had no intention of winning some sort of award for
optimized code.
I am working on an updated version that has optimized versions of some
of the routines, and also command line options and wildcards.
>I guess this is a bit unfair of me to take a pot-shot like this, since I
> didn't look at the code in detail (and I apologize for that), but I encourage
> you to look more closely at it yourself to see if you can optimize it.
>
I'm already on it. It wasn't my original intention to have the most
efficient code in the world, though. :-)
-Jesse
Jesse,
Here is a program that I use to COPY files from within
QuickBasic4.5 that works using 16K "chunks"....
FileIn$ = {filename}
FileOut$ = {filename}
CLS
CLOSE
OPEN FileIn$ FOR BINARY AS 1
OPEN FileOut$ FOR OUTPUT AS 2
K16 = 16384
Data$ = INPUT$(K16, 1)
WHILE Data$ <> ""
PRINT #2, Data$;
Data$ = INPUT$(K16, 1)
WEND
CLOSE
Beau Schwabe
bsch...@ionet.net
Splutter!
Try:
IsItOdd% = Var% and 1
Nothing else needed!
dow