HOME - - - - - Lazarus Tutorials TOC - - - - - - Other material for programmers
Delicious.Com Bookmark this on Delicious     StumbleUpon.Com Recommend to StumbleUpon

Looking at what is on your disk

This has good information, and a search button at the bottom of the page

Please don't dismiss it because it isn't full of graphics, scripts, cookies, etc!

This page is "browser friendly". Make your browser window as wide as you want it. The text will flow nicely for you. It is easier to read in a narrow window. With most browsers, pressing plus, minus or zero while the control key (ctrl) is held down will change the texts size. (Enlarge, reduce, restore to default, respectively.) (This is more fully explained, and there's another tip, at my Power Browsing page.)




STOP PRESS: Tutorial Flawed

I apologise. This tutorial is incomplete, and when I tried to use it myself, I found flaws. I don't have time to fix all of them just now. If you read the following, I hope it will point you roughly in the right direction, and be of some use... but I fear it must be taken with a dose of scepticism and caution.

It is NOT a complete waste of space. It may alert you to things you need to be careful about... in particular the existance of TWO "FindFirst"/"FindNext" systems. You need to pick one, and stick to it. At least within any given application. One uses TSearchRecord, not SearchRecord (no T) as an imporatant type. One uses "dos" in the application's "uses" clause; the other doesn't.

While there ARE flaws in the tutorial, I do offer a complete application written with the assistance of the good parts of this tutorial. The application allows you to "collect" a selection of files from a folder. It is, alas, an essential random selection. The application copies one file in "x" from the folder, the "x" specified by the user at run time. So... you can collect copies of one file in 5 or one file in 500, depending on your wants. (One of my IP cams was set up badly, and by the time I noticed, I had a folder with 900,000 images in it! Windows 7, on modest hardware, didn't fall over... but Windows Explorer was "pretty" (understatement!) sluggish!!

The application isn't especially robust, and the copied files will be sent to C:/DE55/Keep, which must exist before the application is run. But, with care, it DOES work! AND you get the source-code too, which may be instructive, and may be a useful basis for a fancier application... one, perhaps, to copy any image from a folder. Any image, that is, that isn't too close to a scene with "nothing" interesting in it. (To create that application would require "trivial" adaptation of the source-code you can download. You would "just" change...

     liTmp:=FindNext(srResultRecord);
     if lwFilesRead mod wKeepHowOften=0 then begin

... to....

     liTmp:=FindNext(srResultRecord);
     if boFileIsInteresting(srResultRecord) then begin

... and write the subroutine "boFileIsInteresting".

Joking aside... that really is "all" you would have to do, and the work done for you is NOT insignificant... although, of course, the writing of boFileIsInteresting would not be a trivial exercise.

Oh. I suppose you'd also need something to set up, during the application's initialization, your definition of "interesting".

But, for what it is worth, this is the link to download DE55, the program to harvest a selection of files from a folder. Download includes the Lazarus sourcecode.



BEWARE....

A number of things conspire when you get into the realms of file handling, to make it easy to trip up. I think part of my problems in creating this, and our problems in general, is that for those of us coming from the Delphi world... and this tutorial derived from a Delphi tutorial, things are nearlY... but not quite... the same, when you want to read the files on some storage device. Oh joy. Oh well. (If it was easy, everyone would be Doing It.)

First of all, because you are interfacing closely with things mostly managed by the operating system, these parts of Lazarus may be somewhat OS dependent. I can only promise that what I describe works in Windows. (And even that promise is subject to the "stop press" above.

Secondly, there seem to be two VERY SIMILAR, but incompatible "answers" out there. It this tutorial...

I am NOT using the PROCEDURE FindFirst, (type)(no T)SearchRecord), or global variable DosError which become available if you put "dos" in your application's "uses" clause.

I AM using the FUNCTION FindFirst, (type)TSearchRecord which are available if you have not put "dos" in your application's "uses" clause.

BEWARE: My page does not have all the answers. As you read other pages on these matters, REMEMBER that there are the TWO, VERY SIMILAR "systems" out there to trip you up. Sigh.



For what it is worth...

This tutorial explores the provisions built into Lazarus (and Delphi and Pascal) for looking at what files and folders are on your hard disk. The same techniques apply to other forms of backing store, for instance thumb-drives, aka USB memory sticks.

See also "Using "FindFirst" and "FindNext" in Lazarus programming" for another tutorial addressing many of the things addressed in this one.


Before we look at how to do it...

Before we get into the details of looking at what is on a storage device, a few preparatory remarks:

For a very long time, we have been using a system of folders, also known as directories, to organize what is on our storage devices. Any storage device has a "root" folder. Within that, there can be files and further folders. Within the folders-in-a-folder (aka "subdirectories", or subordinate folders) there can be files and/ or further folders. ("Sub-sub-folders"!) There are limits to how deeply folders can be nested, but you are unlikely to hit the limits unless you get silly.

In the past decade or so, the actual story has become somewhat more complicated because Microsoft and others decided that "virtual folders", such as "My Documents", would be a good idea. Happily, the tools we use to look what is on storage devices don't much care if they are pointed at a "real" folder or a virtual one. In the same way, whereas once upon a time, "C:" referred to a specific physical storage device, usually a disk of some type in those days, today one physical disk may be "seen" by the operating system as one or more virtual drives. But again, our folder/ file tools don't care.

Before I start on the details, I will tell you that I am not, in this essay, going to talk at all about how we look inside files. For this essay, all we will do is learn about how you can find out what folders and files are present. We will learn about how to examine their attributes, though. (E.g. when the file was saved, what it's extension is, how big it is.)

So... What are the tools?

The tools for learning about what is on your disk are....

... and having used these, you will also need....

Apart from those basics, there functions to tell you things about a specific folder or file, or to change the attributes recorded for it. For instance, once you program is "pointed" at a particular file, by putting the right stuff in the variable dfMyFile, you can use

showmessage(inttostr(FileSize(myFile));

("IntToStr" is a widely useful function that turns numbers into strings. Showmessage needs a string, FileSize returns a number, hence the need here for InToStr.)

Speaking of Data Types...

If I were able to find an antique called a "typewriter", I could put ink on a page by a rather primitive technique. And if I typed...

1 2 3 One Two Three

The typewriter wouldn't make any distinction between the "1" and the "One", other than the fact that the latter would take three keypresses.

Very early on in learning to program, you learned about data types. If you need a variable to save, say, the number of "things" in a box, that variable has to of the numeric type. But a variable to record what the things in the box are, say, perhaps they are wrackspurts, (extra points if, without Google, you know what they are), then that data goes into a string type variable.

You probably already know that "numeric" and "string" are mere categories of data types. "Numeric", in particular, subdivides in a very tedious manner. What was wrong with what we knew from when we could say now we are six, and knew a "number" from other things? Computers. Bah.

But I digress.

I started talking about "data types" because to look at what is on your hard drive, we need some data types you may not have come across. In one sense, of course everything in the computer is either a number, or a set of numbers... but you will do well to "forget" that the contents of variables of data types like THandle are numbers. You don't really need to know what is in, say, a type THandle variable... you just need to know how to fill the variable before you need to use whatever is in it, whatever that might be. (!)

And, lastly, you need to know about records. They are a bit like a "super" datatype. A record is a set of variables, "gathered together" by the "magic" of the idea of a "record". (It is similar, in some ways, to what database people mean when they speak of "a record", but not exactly the same, so if you have some knowledge of database work, be prepared to keep two meanings for "record" in your head.)

Records

Here begins a brief diversion, for people who have not met "records", as used in programming.

Suppose you had to keep track of a small collection of valuable gem stones. You might set up the following arrays....


Weight[0]:= 5 Value:=5000 Color[0]:=clear   Name[0]:=diamond
Weight[1]:=80 Value:=  10 Color[1]:=red     Name[1]:=garnet
Weight[2]:=20 Value:= 200 Color[2]:=sapphire Name[2]:=blue
etc.......

Once you had the data stored like that, you could write a program to, say, add up the value of all the stones, list all the red stones, etc, etc.

But if you did any sorting, it would be a real pain to write the code.

And so the "record" was born. You define records as follows (You also, and that's why we're going into this, use records defined by the system.)

TGem = Record
  Weight: integer;
  Value: integer;
  Color: string[10];
  Name: string[15];
  end;//Of definition of "TGem"

The above creates a new data type, of the "record" kind. Before I could store my first stone's description in the computer, I would need a variable of "type" TGem. (I didn't have to name my record type "TGem", I could have called it "fred8"... but using a name beginning "T" is good, to emphasize that I've made a new data type.)

I can create "ordinary" variable of type TGem, and I can create arrays, just as with "ordinary" types, like "integer".

So.... after...

var recTmpGem:TGem;
    recGem:array [0..5] of TGem;

.. I can start putting "stuff" in. And I do that by either of the following....

recTmpGem.Weight:=5;
recTmpGem.Value:=5000;
recTmpGem.Color:='clear';
recTmpGem.Name:='diamond';

...or... (achieves same thing).

with recTmpGem do
  Weight:=5;
  Value:=5000;
  Color:='clear';
  Name:='diamond';
end;//of "with"

So far, so boring. As bad as separate arrays.

But! Having done the above, we can now do....

recGem[0]:=recTmpGem;

THAT is as good as....

recGem[0].Weight:=recTmpGem.Weight;
recGem[0].Value:=recTmpGem.Value;
recGem[0].Color:=recTmpGem.Color;
recGem[0].Name:=recTmpGem.Name;

... a significant improvement, in several ways, not just the obvious ones.

Records Are Good. Try them... you will like them.

To recap, in different words:

A "record" is a structured data type. It gathers together some variables, not always all of the same type, into something that itself behaves as if it were a data type. When you have defined (or been given) a record data type, and created some variables of that type, you have a nice tidy way of dealing with a bunch of related "things" (pieces of data).

What does this have to do with files and folders???

For a given file, you can ask....

That data is going to be put in various variables, but they are collected up into records, to make life easy (really... it WILL be easy... once you are happy with "records"!)

Nearly there....

Two things need attention before we get to the heart of this tutorial.

File "names"...

One is easy: I spoke a moment ago of "the name" of a file or folder.

The full name might look like....

C:\Users\TBoyd\Documents\aaText\ToDoList.txt

That can be broken up into....

Simple. Moving on....

Handles...

A handle is "really" "just" a number... inside the computer. But you don't want to know what the number is. You can forget it is a number.

Don't skim the next bit. If you get it firmly into your brain, what follows will be a lot easier.

Handles are used for connecting to things. Or for "grasping them"- hence, I suppose, the name!

Suppose you wanted to erase everything on your hard drive. There is probably a command for doing that. And it would probably work something like this....

var dhDriveID:TDriveHandle;

procedure WipeIt;
  dhDriveID:=ConnectToDrive('C');
  WipeDrive(dhDriveID);
  end;

I've made up bits of a very plausible language to write the above. Everything in it except "dhDriveID" and "WipeIt" would be "built into" the language (reserved works, or "keywords").

But, made up or not, it illustrates the use of a handle. Thus...

The (imaginary) system function "ConnectToDrive" needs to know the human friendly name of the drive you want to connect to. In the example, I've told WipeIt to connect to what we humans call "drive C".

ConnectToDrive returns... something. We don't need to know a lot about the "something", as long as we know that it is of type TDriveHandle, and we give it a variable of type TDriveHandle to go into... as I have, in the above.

Then we come to the command WipeDrive.

It, like "procedure" and "end" and "showmessage", etc, etc, is part of my imaginary, Lazarus-like language.

It is a procedure, and it has a parameter. To use WipeDrive you have to give it not the human friendly name of the drive you want to wipe, but the computer friendly file handle to connect to that drive. Where do you get that? With "ConnectToDrive"!

You don't need to know the details of what is in dhDriveID! You just have to know how to fill it (ConnectToDrive), so that it IS filled with the right stuff when you come to use it (WipeDrive).

Okay... re-read the stuff about "handles" if it wasn't clear, and then, at last, we get to Looking At Files And Folders....

Seeing what files and folders are on your drive

Lazrus comes with a useful record-type called TSearchRec "built in". There's also ANOTHER type, this one (badly) named "SearchRec" (no t). It is available if you've put "dos" in your Uses list... which you SHOULD NOT DO, if following my advice. (If you do, other things come into the picture, including, perhaps, a redefined FindFirst. We need the "standard" on... to do things "our" way. (The other way probably works... but you CAN'T write an application which tries to mix elements from the two "answers".

If you execute the procedure below, FirstFile (a word chosen by us, not a "built in" word)...

procedure TDE55f1.FirstFile;
var liTmp:longint;
    srResultRecord:TSearchRec;//TSearchRec is a standard Lazarus type
begin
  liTmp:=FindFirst('C:/*.*', faAnyFile, srResultRecord);
  if liTmp=0 then
    {See: http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/findfirst.html
      liTmp will be zero if there is no error ON A WINDOWS MACHINE, but
      -1 if no error on a Unix-like platform. Sigh.}
      showmessage('First file/folder found was:'+srResultRecord.name)
    else showmessage('No file or folder exists matching those filters');
  FindClose(srResultRecord);
  end;

...then you will get a lot of data into the record "srResultRecord". Besides FirstFile, 'C:/*.*', liTmp and srResultRecord, everything you see in the code fragment is "built in" to Lazarus and Delphi.... as long has you have added "dos" to your application's "uses" clause.

We'll start at the end, to get something simple out of the way: Any time you use "FindFirst", use "FindClose" once, at the end of what you were doing. Moving on...

One of the "parts" (fields) of any variable of type "TSearchRec" is "name". And that's a string type variable. That's why our...

showmessage('First file/folder found was:'+
     srResultRecord.name)

... works.

We might get the name of a folder in the root of the C drive... for the purposes of FindFirst (and FindNext, which we will come to), folders are almost the same thing as files.

Let's look a little more closely at...

FindFirst('C:/*.*', faAnyFile, srResultRecord);

The first parameter, we used 'C:/*.*', is a filter on the name of the file or folder. With '*.*' we said, give us any file or folder. Had we said 'My*', we would have been saying "give me the first file or folder you can find with a name beginning with "My"

The second parameter, we used faAnyFile, is a filter on the sort of file or folder we want. "faAnyFile" is a system defined constant. Like handles, these constants do "boil down" to numbers, but don't try to figure out what the number might be. There are lists, e.g. (for FindFirst and FindNext) start at http://lazarus-ccr.sourceforge.net/docs..... and if you can find the list of what constants are available to use for different parameters, please get in touch with the URL! We could also have used, say, faReadOnly for the second parameter... that would have given us one of the read-only files from the directory we specified.

The last parameter, we used srResultRecord: A variable has to be provided for the last parameter. It has to be of the system-defined type "TSearchRec" (NOT "SearchRec", which would be right for the "answer" using the "dos" unit. Sigh.) With FindFirst, the variable doesn't need anything in it before the call of FindFirst.

A bit of bad news

Yes, but WHICH file will FindFirst give us?

Apart from knowing that the file will respect the filters demanded by the first two parameter, we don't know which file we will fetch. One will be grabbed "out of the hat". We will just have to Deal With It. (Which I will, later in this tutorial, show you how to do.)

What if there IS no file matching our filters?

Well, the code I showed you was, of course, stripped down. Here's something a little better, which will deal sensibly with cases where no file meets the filter requirements....

Don't be fooled into thinking that FindFirst only collects one bit of data, i.e. "Is there a file or folder like that?". While that bit of data is what is returned by the function, when the function was called, various bits (the proper term is "fields") of srSearchRecord were filled in with data. ("Had values assigned").... Which bring me neatly to...

What are the fields of a record of type TSearchRec?

For a comprehensive answer to that, visit http://lazarus-ccr.sourceforge.net/docs....

These are some of the fields which are important to me...

BEWARE: the encoding used for the Time field varies from OS to OS... and, for one consequence, you can't do simple arithmetic with it, at least in the Windows environment. (The value in "Time" for a new file may OR MAY NOT) be greater (or lesser) than the value for an old file. The answer is to use filedatetodatetime() to convert ".Time" values into values encoded as TDateTime values. Sigh.

There is another very important field which is filled when you call FindFirst: the FindHandle field. There is no reason for you ever to look at what is in FindHandle! But!.... it needs something in it before you use FindNext. Which we are going to talk about next. You should not use what's in the field yourself... but FindNext will be using it... don't worry about using FindNext... that's allowed!

The FindNext function. (type: LongInt)

Imagine for me that you have a hard disk connected to your operating system as "C:", and that in it's root ("C:\"), you have at least 5 files or folders...

Given that situation, consider...

var srResultRecord:TSearchRec;
    iLoop: integer;
    liTmp:LongInt;

procedure ListFive;
begin
  liTmp:=FindFirst('C:\*.*', faAnyFile, srResultRecord);

  if liTmp=0{and you are using Windows... use -1 for Linux}

  then showmessage('First file/folder found was:'+srResultRecord.name);

  for iLoop:=1 to 4 do begin
     liTmp:=FindNext('*', faAnyFile, srResultRecord);
     showmessage('Next file/folder found was:'+srResultRecord.name);
     end;//"for..."

  FindClose(srResultRecord);
  end;//ListFive

That is a simplified illustration of how to read more than one file from the storage device. It doesn't deal with the possibility that there may not be five files or folders to read. But other than that, it is complete.

Note in particular: FindFirst doesn't need anything in srResultRecord.FindHandle. But once you have called FindFirst, as long as it returned "true", you will have something sensible in the FindHandle field. WITH something sensible in the FindHandle field, you can then call FindNext. It will, as the name suggests, pick up the data relating to another file or folder, and change what is in FindHandle, so that if you call FindNext again, you will get another file or folder.

"FindFirst" and "FindNext" are not the names I would have used because the file/folder names do not come out of the program above in any useful order. "FindOne" and "FindAnother" might have been better names.

To recap:

You make a call of FindFirst to make a start. You then call FindNext as often as suits you, to pick up the details of further files and folders. You must then call FindClose, to "tie up" various loose ends.

Both FindFirst and FindNext are longint functions. (In Delphi, they are booleans... and there are variations on the FindFirst and FindNext routines which come into play if... against my advice!... you have "dos" in your "uses" clause.)

Under windows, they return zero if the attempt to find something was successful. Under Unix-like OSs (Linux, etc), they return -1 if the attempt to find something was successful.

FindNext won't "re-find" the thing FindFirst found. FindNext won't "find" any file or folder it has previously found. (Well, found since the program started, or a FindClose was done.)

Two special folders

I'm not sure that FindFirst or FindNext will find both, or even one of these, but just in case...

If ever you come across a folder called ".", or one called "..", interpret that to mean "the folder we are "in" at the moment", or "the "parent" of the folder we are in at the moment", respectively.

Getting the files in order

Earlier, I promised to talk about how you get the files in a logical order... sorted by name, by date, etc.

I'm afraid there's nothing for it but to fetch all of the files which meet your selection criteria, putting the facts about those files in arrays, or maybe in a memo (which is an array, too, of course), and then sorting them "by hand". Sorry!

Already mentioned, but... concerning the "Time" value

After you call FindFirst or FindNext, you will have something in the Time field of the returned record. It will give you the time and date of the last time the file was modified.

However, beware: the encoding of date and time into an integer varies from OS to OS. And, at least under Windows, you can't use the raw ".Time" value in arithmetic. If you fetch .Time for two different files, you can't directly, from those values, tell which file is older.

However, if you apply the filedatetodatetime() function to the value returned in .Time, the result will be a TDateTime value, which is easier to work with. And Lazarus (and Delphi) have many functions and procedures for working with TDateTime values. (And a bigger TDateTime is always a newer (i.e. "later") TDateTime.)

Stop press... I discovered problems in the above, and spent time on it. Problems may remain in the following... please get in touch if you encounter difficulties.

Simple information about files and folders

The following functions can be useful...

When you have the full spec of a file, e.g.

... the following can be useful for parsing it...

Manipulating Files and folders... copy, move, delete, etc.

So... that's how you find out "what's there"

There are useful routines built into Lazarus and Delphi for working with files and folders.

Working with what is inside a file is a big story for another day. (I've already done tut

In Delphi days, to "do things" to the file or folder as an unopened entity, there were things like...

For a list of the DELPHI subroutines...http://www.delphibasics.co.uk/ByFunction.asp?Main=Files&Sub=Control

I'm not sure how many have had Lazarus equivalents provided.





            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"

If you visit 1&1's site from here, it helps me. They host my website, and I wouldn't put this link up for them if I wasn't happy with their service. They offer things for the beginner and the corporation.www.1and1.com icon

Ad from page's editor: Yes.. I do enjoy compiling these things for you. I hope they are helpful. However... this doesn't pay my bills!!! Sheepdog Software (tm) is supposed to help do that, so if you found this stuff useful, (and you run a Windows or MS-DOS PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Link to Lazarus Tutorials main page
How to contact the editor of this page, Tom Boyd


Valid HTML 4.01 Transitional Page tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes. There were two "unknown attributes" in Google+ button code. Sigh.


If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....