Amikor az ember nagy adatmennyiséget másol, kissé lassúnak tűnnek a Norton vagy Intéző típusú másoló programok. A Dos Navigator ugyan megoldás lehet, de vannak emberek - többek között én is ilyen vagyok –, akiknek már a Dos Navigator nevének hallatán futkosni kezd a hátán a szőr.

Mit tehetünk hát, ha szeretnénk egy gyors másolóprogramot? Írjunk egyet (elvégre ez itt egy programozási rovat)!

Lássuk csak, mit is szeretnénk!

Egy olyan másoló programot szeretnénk írni, amely egyszerre több fájlt másol, és lehetőleg ezt jó gyorsan teszi. Ha megnézzük, a DN úgy másol, hogy beolvas a memóriába egy rakás fájlt, majd szépen kiírja a célhelyre. Ezt fogjuk mi is tenni.

A programnak első lépésben fel kell építenie egy fájl- és egy könyvtárlistát. A könyvtárlistára azért van szükség, mert ha könyvtárakat másolunk, akkor a célhelyen létre kell hozni a könyvtárakat. Az én verziómban két külön dinamikus listát használunk a fájl- és könyvtárnevek eltárolására. A listákat egy könyvtárfa-bejáró procedúra építi fel.

Második lépésben történik a másolás. A másolást addig tesszük, amíg ki nem fogyunk a fájlnevekből. A program szorgosan elkezdi olvasni a fájlokat, majd ha megtelt a memória, az egészet kimásolja winchesterre, a célterületre és kiüríti a memóriát.

Ha mindennel készen vagyunk, a memóriából kirúgjuk a fájllistát is.

Ez volna a program nagyvonalakban. Most lássuk, miként kell ezt megírni Pascalban!

Adattípusok
 
Type Type kulcsszó
PtreeList=^TTreelist; 
TtreeList=Record  
  Text: String[80]; 
  Next: PtreeList; 
End; 
 
Könyvtárlistaelem 
Ez a könyvtárlistát tartalmazó dinamikus lista leírása. 
A TtreeList Text mezőjében lesz a létrehozandó könyvtár neve (a forrás könyvtárhoz viszonyítva)
Ppiece=^Tpiece; 
Tpiece=Record 
  Piece: Pointer; 
  Size : Word; 
  Next : Ppiece; 
End; 
 
Fájldarabka 
Ilyen rekordokból álló lista lesz a memóriában egy fájl. A Piece pointer a fájl darabjának címét, a size a méretét és a Next a következő fájldarabka címét tartalmazza.
PfileList=^Tfilelist; TfileList=Record 
  Text: String[80]; 
  FirstP: Ppiece; 
  ActPos: LongInt; 
  Ready: Boolean; 
  saved: Boolean; 
  Next: PfileList; 
End; 
 
Fájllistaelem 
Ilyen rekordokban tároljuk a fájlokra jellemző információkat: 
a Text a fájl neve és útvonala (a forrás könyvtárhoz viszonyítva),  
a FirstP tartalmazza az első fájldarabka címét,  
az ActPos tartalmazza a fájlból eddig beolvasott hosszt, 
ha a ready mező értéke True, akkor ezt a fájlt már átmásoltuk,  
ha a saved mező értéke True, akkot a fájlt már létrehoztuk a célterületen.
Fabejáró procedúra (ez építi fel a fájl- és könyvtárlistát)

Procedure buildfilelist(cc:string; Var FTree: PTreeList;Var FFile: PFileList);

Var
  LTree: PTreeList;
  LFile: PFileList;

 procedure fabejar(c: String);
 Var
   s: SearchRec;
   t: String;
   i: Integer;
   P1: PFileList;
   P2: PTreeList;
 Begin
   t:=c; if ((length(t)>3) and (t[length(t)]='\')) then t:=copy(t,1,length(t)-1);
   chdir(t);
   if ioresult<>0 then exit;
   New(P2);
   P2^.Text:=copy(c,length(cc)+1,255);
   P2^.Next:=Nil; if LTree<>Nil then LTree^.Next:=P2 Else FTree:=P2;
   LTree:=P2;
   s.name:='';
   FindFirst('*.*',$3f,s);
   While s.name<>'' do
   Begin
     if s.name[1]<>'.' then
     Begin
       if (s.attr and $10)<>0 then
       Begin
         {$I-}
         chdir(s.name);
         {$I+}
         if ioresult=0 then
         Begin
           GetDir(0,t);
           fabejar(t);
         End;
       End
       Else
       Begin
         if (s.attr and 24)=0 then
         Begin
           if c[length(c)]<>'\' then c:=c+'\';
           New(P1);
           P1^.FirstP:=Nil;
           P1^.ActPos:=0;
           P1^.ready:=false;
           P1^.saved:=false;
           P1^.Text:=copy(c,length(cc)+1,255)+s.name;
           P1^.Next:=Nil; if LFile<>Nil then LFile^.Next:=P1 Else FFile:=P1;
           LFile:=P1;
         End;
       End;
     End;
     s.name:='';
     FindNext(s);
   End;
   {$I-}
   chdir('..');
   {$I+}
   if ioresult<>0 then exit;
 End;

Begin
  LTree:=FTree; if LTree<>Nil then While LTree^.Next<>Nil do LTree:=LTree^.Next;
  LFile:=FFile; if LFile<>Nil then While LFile^.Next<>Nil do LFile:=LFile^.Next;
  fabejar(cc);
End;

A bemenő paraméterek:

A rutin első körben elmegy a könyvtár és a fájl lista végére (gyakorlatilag hozzáfűz a meglévő listához, ha van ilyen) majd meghívja a tulajdonképpeni fabejáró rutint.

A rekurzív rutin, miután ellenőrizte, hogy a paraméterben megkapott könyvtárnév helyes-s beírja a paraméterét a könyvtárlista végére, majd a paraméterben megkapott könyvtárat végignézi, a fájlokat felveszi a fájllistába, ha könyvtárat talál, meghívja saját magát, de a könyvtár nevét adva paraméternek. Ennyi.

Maga a másolást végző rutin.

Procedure Copier(source,destination: String);

Var
  FirstTree: PTreeList;
  FirstFile: PFileList;
  P1: PTreeList;
  P2,P2a: PFileList;
  P3,P3a: Ppiece;
  size: Word;
  LastP: Ppiece;
  f: File;

Begin
  A program beállítja a fájl- és könyvtárlistát, majd meghívja a fabejáró procedúrát
  FirstTree:=Nil; FirstFile:=Nil;
  BuildFileList(source,FirstTree,FirstFile);
  A könyvtárak létrehozása a célhelyen
  P1:=FirstTree;
  if P1<>Nil then P1:=P1^.Next;
  While P1<>Nil do
  Begin
    {$I-}
    MkDir(destination+P1^.Text);
    {$I+}
    if ioresult<>0 then ;
    P1:=P1^.Next;
  End;

  Itt történik a másolás
  P2:=FirstFile;
  Elindulunk a fájllista elejéről…
  While P2<>Nil do
  Begin
    P2:=FirstFile;
    Addig olvasunk, amíg a fájllista végére érünk vagy elfogy a memóriánk.
    While ((P2<>Nil) and (maxavail>50000)) do
    Begin
      Megnyitjuk az éppen következő fájlt.
      Assign(f,source+P2^.Text);
      filemode:=0; {megnyitás csak olvasásra}
      Reset(f,1);
      Előfordulhat, hogy egy fájl nem fér be teljes egészében a memóriába. Ilyenkor az ActPos mező értéke nem nulla,
      hanem az a szám, ameddig már megvan a célhelyen, vagyis a fájlt csak ActPos-tól kezdődően kell beolvasni.
      Seek(f,P2^.ActPos);
      LastP:=Nil;
      Itt olvassuk be az aktuális fájlt. Addig olvasunk, amíg el nem fogy a memória vagy a fájl végére nem érünk.
      A fájlt 49Kbyte-os darabokban olvassuk.
      While ((Not Eof(f)) and (maxavail>50000)) do
      Begin
        Kiszámoljuk, hogy mekkora darabot kell beolvasni a fájlból
        if filesize(f)-filepos(f)>49152 then size:=49152 else size:=filesize(f)-filepos(f);
        Memóriát foglalunk a következő fájldarabnak (P3)
        New(P3);
        Az új darabot hozzáfűzzük a már meglévő fájldarabkákhoz
        P3^.Next:=Nil;
        if LastP<>Nil then LastP^.Next:=P3 else P2^.FirstP:=P3;
        LastP:=P3;
        Beállítjuk az új darabka adatait
        P3^.Size:=size;
        Memóriát foglalunk az új fájldarabkához tartozó fájldarabnak
        GetMem(P3^.piece,size);
        Beolvassuk a fájl következő részét.
        BlockRead(f,P3^.piece^,size);
        Frissítjük a fájlhoz tartozó információkat. A ready mező akkor lesz igaz, ha a fájlt teljes egészében beolvastuk.
        P2^.ActPos:=P2^.ActPos+size;
        P2^.ready:=P2^.ActPos=filesize(f);
      End;
      Lezárjuk az éppen aktuális fájlt, és továbblépünk a következőre.
      Close(f);
      P2:=P2^.Next;
      Ha van még memóriánk és a következő fájl sem NIL (P2) akkor a ciklus kezdődik előről.
    End;

    A beolvasott adatok kiírása
    filemode:=2;
    P2:=FirstFile;
    A ciklus addig megy, amíg van olyan fájlunk, amiből van egy darab a memóriában.
    While (P2<>Nil) and (P2^.ActPos<>0) do
    Begin
      Assign(f,destination+P2^.Text);
      Megnyitjuk a fájlt. Ha a saved mező értéke igaz, akkor a fájlnak már van darabja a célhelyen vagyis
      a fájlt csak továbbírni kell, nem pedig létrehozni.
      if P2^.saved then
      Begin
        Reset(f,1);
        Seek(f,filesize(f));
      End
      Else
      Begin
        Ha a saved értéke hamis, akkor létrehozzuk a fájlt a célhelyen és a saved-et igazra állítjuk, jelezve,
        hogy ha legközelebb megnyitjuk a fájlt akkor továbbírni kell, nem pedig létrehozni. Egyébként ennek
        csak olyan fájloknál van jelentősége, melyek nem fértek bele egyben a memóriába.
        P2^.saved:=true;
        ReWrite(f,1);
      End;
      Ha megnyitottuk a fájlt, az első darabjától kezdve kimásoljuk a winchesterre.
      P3:=P2^.FirstP;
      While P3<>Nil do
      Begin
        BlockWrite(f,P3^.piece^,P3^.Size);
        P3:=P3^.Next;
      End;
      Mikor kiírtuk a fájlt a célhelyre, lezárjuk és nekiállunk a következőnek (már ha van következő)
      Close(f);
      P2:=P2^.next;
    End;

    Nem maradt más hátra, mint hogy kiürítsük a memóriát a következőkben beolvasandó fájlok
    számára. Elindulunk az első fájltól és ha a fájl ready mezője igaz (vagyis teljes egészében átmásoltuk),
    töröljük a fájlhoz tartozó darabkákat és a fájlt a fájllistából.
    P2:=FirstFile;
    {$B-}
    While ((P2<>Nil) and (P2^.ready)) do
    Begin
      Fájldarabkák törlése a memóriából
      P3:=P2^.FirstP;
      P2^.FirstP:=Nil;
      While P3<>Nil do
      Begin
        FreeMem(P3^.Piece,P3^.Size);
        P3a:=P3^.Next;
        Dispose(P3);
        P3:=P3a;
      End;
      Fájl kivétele a fájllistából
      P2a:=P2;
      P2:=P2^.Next;
      Dispose(P2a);
      FirstFile:=P2;
    End;
    {$B-}
    A memóriában szinte mindig marad egy fájl, amit csak félig sikerült beolvasni, mert közben elfogyott a memória.
    Ennek a darabjait is ki kell rúgni a tárból mivel előfordulhat, hogy ha nem rúgjuk ki, nem marad hely a következő
    fájok beolvasására.
    if ((FirstFile<>Nil) and (FirstFile^.ready=false) and (firstfile^.actpos<>0)) then
    Begin
      P3:=FirstFile^.FirstP;
      FirstFile^.FirstP:=Nil;
      While P3<>Nil do
      Begin
        FreeMem(P3^.Piece,P3^.Size);
        P3a:=P3^.Next;
        Dispose(P3);
        P3:=P3a;
      End;
    End;
  End;
End;

Néhány további információ:

Filemode változó
a filemode változó a pascal implicit (nem kell definiálni) változója, ami a fájlkezelés módját szabályozza. Köztudott, hogy ha egy írásvédett fájlt akarunk megnyitni, akkor a pascal a nem éppen barátságos FILE ACCES DENIED - fájlelérés megtagadva hibaüzenettel küldi el a programozót - és a felhasználót - melegebb égtájakra. Ezt a problémát lehet úgy kiküszöbölni, hogy a filemode változót a fájl megnyitása előtt 0-ra állítjuk jelezve a pascalnak, hogy mi csak olvasni szeretnénk azt.

FILEMODE változó
    0: Fájl megnyitás csak olvasásra
    1: Fájl megnyitás csak írásra (ennek mi lehet az értelme?)
    2: Fájl megnyitása írásra és olvasásra (ez az alapértelmezés)

{$B+|-} direktíva
Ez a fordítódirektíva a logikai kifejezések kiértékelésének módját szabályozza. Ha be van kapcsolva, akkor a Pascal teljesen kiértékeli a logikai kifejezéseket, míg ha ki van kapcsolva, akkor a Pascal csak addig töri magát, amíg egyértelműen el tudja dönteni a kifejezés végleges értékét. Például egy AND kifejezésnek ha az első tagja hamis, akkor már nem is kell törődni a második taggal. Gyakorlati haszna csak védett módban szerintem van, segít megelőzni a Runtime Error 216 - General Protection Fault típusú hibaüzeneteket.

Hát remélem elég érthetőre sikerült fogalmaznom a mondanivalómat. Ha valami kérdésed van a cikkel, vagy bármely más, programozással kapcsolatos témával kapcsolatban, írd meg email címünkre: PC-XUSER@FREEMAIL.C3.HU! Ha tudunk, szívesen írunk a témáról.

Ja, és még valami: Ha a programot protected módban fordítod be, képes lesz a számítógépedben található összes memóriát kihasználni és így, memóriádtól függően akár 128 Megás darabokban másolni a fájlokat.

A cikkhez tartozik egy forráskód is: COPIER.PAS
A forráskód ugyanezen másolóprogram néhány egyéb cafranggal kiegészítve úgy, hogy a forrás- és célkönyvtárat paraméterben lehet neki átadni.

 
GRT
eMail:PC-XUser@FREEMAIL.C3.HU, Subject: "XVision rovat"