|
|
Tetszőleges számú állomány összecsomagolása egy állományba
Állomány csomagoló 1. rész
|
|
Példaprogram letöltése
14446 bájt
|
Állománytömörítő vagy archiváló programoknál jól jöhet egy olyan komponens, amely korlátlan számú fájlt tud összemásolni egyetlen fájlba, majd ebből kibontani az összes benne lévő állományt. Mellékelt példában az összemásoló komponenst készítjük el, míg jövő heti cikkünkben azt, mely kibontja ezt az állományt.
A mellékelt példaprogram megnyitása előtt a PackFiles.pas-ban lévő komponenst telepítenie kell a Delphi alá.
A komponenshez tartozik egy pckfiles.pas nevű unit, amely a property editor-okat, a konstansokat és a fájl formátumához tartozó típusdeklarációkat tartalmazza. Nézzük először ezeket meg!
Az összepakolt fájlban a következő adatokra van szükségünk:
- egy fájl azonosító, amelyből egyértelműen megtudjuk, hogy egy általunk összepakolt fájlról van-e szó;
- egy verziószám, amely elsősorban arra szolgál, hogy újabb verziójú programmal összemásolt fájlokat a régebbi szétcsomagoló programmal ne lehessen megnyitni;
- a fejléc adatokat tartalmazó adatblokk hossza. Ezt azért kell külön eltárolni a fájlban, hogy esetleges későbbi fejlesztéseknél a fájl információkat tartalmazó rekordot bármikor kiegészíthessük további adatokkal;
A fenti adatokat egy TPackedFileHeader rekordban tároljuk:
TPackedFileHeader = record
ID:integer;
Version:integer;
InfoSize:integer;
end;
Azután tárolhatjuk az összepakolt fájl adatait:
- a fájlok adatait tartalmazó rekordok hossza;
- az első fájl kezdőpozíciója az összepakolt fájlon belül;
- az összepakolt fájlok száma;
- a fájl elkészítésének dátuma és időpontja;
- flagek, amikkel különböző opciók tárolhatók a fájlban. A jelenlegei verzióban ez nincs használva, de későbbi fejlesztéseknél nagyon jól felhasználható olyan információk jelzésére, hogy pl. a fájl titkosított-e vagy sem;
Ezeket az adatokat egy TPackedFileInfo rekordban tároljuk:
TPackedFileInfo = record
FileTblRecSize:integer;
StartPosition:cardinal;
NumberOfFiles:integer;
DateStamp:TDateTime;
Flags:integer;
end;
Ezek voltak a fejléc adatai. A fejléc után tároljuk el az összepakolt fájlok adatait. A fájlok adatait tartalmazó rekordok egymás után vannak eltárolva táblázatszerűen.
- a fájl pozíciója az összepakolt fájlban. Ehhez hozzá kell adni a fejlécben tárolt kezdő pozíciót!
- a fájl mérete bájtokban.
- a fájl mérete a tömörítés után. Ez ebben a verzióban még nem használatos, hiszen az összepakolás során a fájlokat nem tömörítjük!;
- a fájl neve az eredeti elérési útvonallal együtt;
Az összepakolt fájlok adatait egy-egy TFileTableHeader rekordban tároljuk:
TFileTableHeader = record
FilePosition:cardinal;
FileSize:cardinal;
PackedSize:cardinal;
FileName:string[PFMAX_FILENAME_LENGTH];
end;
A komponens Files property-jében kell megadni az összepakolandó fájlokat. Ez egy TCollection osztályból származtatott property, tehát a felvehető fájlok száma nincs korlátozva. A FileName property-ben kell megadni az összepakolt fájl nevét. A fájl kiterjesztése bármi lehet, hiszen nem ez alapján fogjuk azonosítani a fájlt a kicsomagoláskor.
A fájlok összemásolása az Execute függvény meghívásával kezdődik. A függvény visszatérési értéke 0, ha sikerült a művelet, egyébként a hiba kódja, amit a pckfiles.pas fájlban deklarált PFERROR kezdetű konstansokkal lehet azonosítani.
function Execute:integer;
Az összemásolás a következő lépésekből áll:
- létrehozunk egy átmeneti fájlt, amibe bemásoljuk egymás után az összemásolandó fájlokat, miközben eltároljuk a kezdőpozíciójukat;
- létrehozzuk az output fájlunkat, amibe először bemásoljuk a fejléc adatokat, majd az összemásolt fájlok adatait tartalmazó rekordokat;
- az output fájlba bemásoljuk az első lépésben létrehozott átmeneti fájl tartalmát.
Az összemásolás első lépéseként ellenőrizzük, hogy a célkönyvtár létezik-e. Ha nem létezik, akkor megpróbáljuk létrehozni azt, és ha ez sem sikerült, akkor egy PFERROR_PATH_NOT_FOUND hibaüzenettel kilépünk a függvényből.
if NOT ForceDirectories(ExtractFilePath(FFileName)) then
begin
result:=PFERROR_PATH_NOT_FOUND;
Exit;
end;
A következő lépésben ellenőrizzük, hogy az átmeneti könyvtár létezik-e. Az ExpandEnvironmentStrings függvénnyel lekérdezzük a TEMP környezeti változó értékét, majd ellenőrizzük, hogy az ebben tárolt könyvtár elérési útvonal létezik-e. Ha nem létezik, akkor egy PFERROR_TEMP_PATH_NOT_FOUND hibaüzenettel kilépünk.
GetMem(TD, MAX_PATH);
ExpandEnvironmentStrings(PChar('%TEMP%'), TD, MAX_PATH);
TmpDir:=TD;
FreeMem(TD, MAX_PATH);
if NOT DirectoryExists(TmpDir) then
begin
result:=PFERROR_TEMP_PATH_NOT_FOUND;
Exit;
end;
if TmpDir[length(TmpDir)]<>'\' then TmpDir:=TmpDir+'\';
Ha megvan az átmeneti fájl könyvtára, akkor kreálunk egy nevet az átmeneti fájlnak. Itt nem adhatunk meg konstans értéket, hiszen biztosítani kell, hogy egy időben több összepakolás is történhessen. Ezért a fájl végére még odabiggyesztünk egy számot, és azt addig növeljük, amíg nem találunk ilyen nevű fájlt az átmeneti könyvtárban.
TmpFileName:=TmpDir+'ASPF';
t:=1;
while FileExists(TmpFileName+IntToStr(t)+'.TMP') do inc(t);
TmpFileName:=TmpFileName+IntToStr(t)+'.TMP';
Ha idáig eljutottunk, akkor következhet a fájlok bemásolása az átmeneti fájlba. A t változóban tároljuk az éppen másolandó fájl sorszámát, a FilesCopied változóban a sikeresen bemásolt fájlok számát és a Position változóban az aktuális pozíciót.
t:=0;
FilesCopied:=0;
Position:=0;
Megpróbáljuk létrehozni az átmeneti fájlunkat, és ha nem sikerült, akkor egy PFERROR_CANT_CREATE_TEMP_FILE hibakóddal kilépünk. Mint a forrásból is látható, a fájlok kezeléséhez a TFileStream osztályt alkalmazzuk.
try
fdestination:=TFileStream.Create(TmpFileName, fmCreate);
except
Result:=PFERROR_CANT_CREATE_TEMP_FILE;
Exit;
end;
Ha sikerült létrehozni az átmeneti fájlt, akkor elkezdhetjük belemásolni az összemásolandó fájlokat.
while t<FFiles.Count do
begin
Megpróbáljuk megnyitni az aktuális fájlt, és ha sikerül, akkor azonnal bele is másoljuk az átmeneti fájlunkba. Ezután módosítjuk az adott fájl property-jeit, úgy, hogy az FPosition property-be beírjuk a fájl kezdőpozícióját, az FCopied property értékét True-ra állítjuk ezzel jelezve, hogy a fájl sikerült bemásolni, és az FPackedSize property-be pedig beírjuk a fájl méretét (mivel tömörítés nem történik).
try
fsource:=TFileStream.Create(FFiles.Items[t].FileName, fmOpenRead);
fdestination.CopyFrom(fsource, fsource.Size);
with FFiles.Items[t] do
begin
FPosition:=Position;
FCopied:=True;
FPackedSize:=fsource.Size;
end;
A sikeresen bemásolt fájlok száma 1-el nőtt, ezért a FilesCopied változó értékét is növelni kell. A Position változó értékét a bemásolt fájl méretével kell növelni.
inc(FilesCopied);
inc(Position, fsource.Size);
fsource.Free;
Ha nem sikerült megnyitni a fájlt, akkor az FCopied property-t false-ra állítjuk.
except
FFiles.Items[t].FCopied:=False;
end;
inc(t);
end;
fdestination.free;
Mostanra már megvan az átmeneti fájlunk, ami tartalmazza az összes sikeresen bemásolt fájlt. A következő lépés a FileName property-ben megadott output fájl létrehozása lesz. Ha nem sikerült, akkor egy PFERROR_CANT_CREATE_OUTPUT_FILE hibakóddal kilépünk, de előtte még töröljük az átmeneti fájlunkat.
try
fdestination:=TFileStream.Create(FFileName, fmCreate);
except
DeleteFile(TmpFilename);
result:=PFERROR_CANT_CREATE_OUTPUT_FILE;
Exit;
end;
Először a Header (TPackedFileHeader) rekordot írjuk ki, ami a fájl azonosítóját, a fájl verziószámát és a fejléc rekord hosszát tartalmazza.
Header.ID:=PFFILEID;
Header.Version:=PFVERSION;
Header.InfoSize:=SizeOf(Info);
Ezután az összepakolt fájl adatait tartalmazó rekordot (TPackFileInfo) mentjük el:
Info.FileTblRecSize:=SizeOf(FileTblHeader);
Info.NumberOfFiles:=FilesCopied;
Info.StartPosition:=SizeOf(Header)+Header.InfoSize+(Info.FileTblRecSize*Info.NumberOfFiles);
Info.DateStamp:=Date+Time;
Info.Flags:=PFFLAG_NONE;
fdestination.Write(Header, SizeOf(Header));
fdestination.Write(Info, SizeOf(Info));
Ezután következhet a fájlok adatait tartalmazó táblázat tárolása:
t:=0;
while t<FFiles.Count do
begin
if FFiles[t].FCopied then
begin
FillChar(FileTblHeader.FileName, PFMAX_FILENAME_LENGTH, #0);
FileTblHeader.FilePosition:=FFiles[t].FPosition;
FileTblHeader.FileSize:=FFiles[t].FFileSize;
FileTblHeader.PackedSize:=FFiles[t].FPackedSize;
FileTblHeader.FileName:=FFiles[t].FileName;
fdestination.Write(FileTblHeader, SizeOf(FileTblHeader));
end;
inc(t);
end;
Az összepakolt fájl adatait mind kiírtuk, most már csak az átmeneti fájlunkat kell az output fájl végére bemásolni, és készen is vagyunk:
try
fsource:=TFileStream.Create(TmpFileName, fmOpenRead);
fdestination.CopyFrom(fsource, fsource.Size);
except
DeleteFile(TmpFilename);
fdestination.Free;
result:=PFERROR_CANT_COPY_PACKED_FILES;
Exit
end;
fdestination.Free;
fsource.Free;
DeleteFile(TmpFilename);
Ha idáig eljutottunk, akkor a függvény visszatérési értéke 0, ami azt jelenti, hogy sikerült létrehoznunk az összepakolt fájlt.
|
Könyv
Ez a cikk megtalálható ebben a könyvben:
Delphi Software Offline 2001 évkönyv 268. oldal
Felhasználási feltételek
A Software Online szoftverfejlesztői magazin mindegyik cikke, minden megjelent képe, és egyéb publikált anyaga szerzői jog védelme alatt áll! Bármilyen formában történő másodlagos terjesztésük, közzétételük vagy felhasználásuk kizárólag a kiadó előzetes írásbeli engedélyével történhet!
|