Module BigFile
Verfasst: 14.09.2015 20:08
Kennt sicher jeder, man programmiert ein Spiel oder Anwendungen und hat tausende Bilder, JPGs oder andere Dateien, die das Programm benötigt. Zum einem ist es unpraktisch, immer so viele Kleinstdateien mitzuliefen, zum anderen will man nicht, das andere diese Dateien modifizieren.
Man könnte jetzt das ganze in eine Zip oder 7z-Datei einpflegen und mit den entsprechenden Packbefehle das ganze regeln. Problem hier, man müsste immer die Archivdatei aktuell halten, wenn man eine Änderung in den Dateien vornimmt oder man neue hinzufügen will, zum anderen kann die natürlich auch wieder jeder öffnen.
Mein BigFile-Modul soll diese Lücke schließen. Der Code ist so gestaltet, dass wenn eine Datei nicht in Bigfile gefunden wird, dass dann auf das normale Dateisystem ausgewichen wird. Auch gibt es einen Unterschied, ob man mit Debug compiled (meist wohl über Compile&run). Wenn der Debugger läuft, wird davon ausgegangen, das die Dateien in der BigFile veraltet sind und die Dateien aus den Dateisystem werden bevorzugt werden. Ohne Debugger haben die BigFile-Dateien immer Vorrang vor lokalen.
Es ist auch möglich mehrere BigFiles einzulesen. Sollten sich Überschneidungen ergeben, werden immer die Dateien aus der zuletzt eingelesen Bigfile bevorzugt. So kann man sehr einfach Patchfiles kreieren, ohne das man die alte Bigfile komplett ersetzen zu müssen.
Die Größe des Moduls erklärt sich dadurch, das ich im Prinzip die Directory, ReadFile und Preference Befehle nachgebaut habe. So kann man wie gewohnt programmieren, ohne sich zu kümmern, wo die Datei jetzt liegen - schon in einer Bigfile oder in lokale Dateien - es ist egal.
Wie wird eine Bigfile erstellt
Die Erstellungsbefehle werden normalerweise aus"CompilerIf". Sinn ist, das man die Erstellung eigentlich nur braucht, wenn man die BigFiles erstellt, nicht aber beim eigentlichen Programm. Um das Modul "BigFileCreate" zu benutzen, muss man vor den Include-Befehl die Konstante #bigfile_create auf #True setzen.
Danach stehen folgende Proceduren zur Verfügung:
BigFileCreate::Create(file.s)
Erstellt und öffnet eine BigFile.
BigFileCreate::Close()
Schreibt die gepackte Indexinformation in die Datei und schließt sie. Damit ist die BigFile einsatzbereit.
BigFileCreate::AddDir(path.s,BigFileDir.s=".")
Fügt alle Dateien in angegeben Ordner und sämtliche Unterordner zur BigFile. Defaultmäßig werden sie in Hauptverzeichnis (".") hinzugefügt, kann man aber ändern.
BigFileCreate::AddFile(file.s,BigFileDir.s=".")
Fügt eine einzelne Datei dazu. Auch hier kann man den Unterordner in der BigFile angeben.
Wie greife ich auf eine BigFile zu?
Ob die Konstante #bigfile_create #True oder #False ist oder gar definiert ist, hat keine Bedeutung. Einfach nur die "BigFile.pbi" "includen" und fertig. Zugriff erfolgt über folgende Befehle:
BigFile::Load(file.s)
Lädt die Index-Daten der BigFile in den Speicher. Anschließend sind alle Dateien in der BigFile bekannt
BigFile::Catch(*mem)
Die BigFile kann auch schon in Speicher befinden. WICHTIG: Der Speicherbereich darf nicht freigegeben werden!
Das ganze funktioniert natürlich auch, wenn der Speicher sich in der DataSection mittels IncludeBinary sich befindet.
BigFile::Clear()
Löscht sämtliche Indexdateien. Wenn man auf eine BigFile zugreifen will, muss man sie anschließend neu laden.
BigFile::DebugList()
Nur mit Debugger verfügbar. In Debug-Fenster werden sämtliche Dateien ausgegeben, die in der BigFile sind.
BigFile::SetPath(path.s)
Wenn die Dateien nicht in der BigFile sind, werden sie CurrentDirectory() gesucht. Hier kann man einen anderen Pfad angeben.
Directory-Befehle
Die folgenden Befehle ersetzen die aus PureBasic bekannten Directory-Befehle. Benutzung ist identisch dazu. FinishExamine ist zwingend notwendig, sonst gibts ein Speicherleck.
Es werden sowohl die Dateien in BigFile als auch die in gesetzten Ordner berücksichtigt!
BigFile::Examine(path.s)
Gibt ein *handle zurück oder #False bei einen Fehler
BigFile::EntryType(*handle)
Gibt je nach Typ #PB_DirectoryEntry_Directory oder #PB_DirectoryEntry_File zurück.
BigFile::NextEntry(*handle)
#True, wenn es einen weiteren Eintrag gab, ansonsten #false.
BigFile::EntryIsBig(*handle)
#True, wenn es sich um eine Datei in der BigFile handelt oder #False, wenn es eine reale Datei ist.
BigFile::EntrySize(*handle)
Die Größe des Eintrags in Bytes.
BigFile::EntryName(*handle)
Gibt einen String mit den Namen zurück.
BigFile::FinishExamine(*handle)
Gibt das *handle wieder frei.
Dateisystembefehle
BigFile::Size(file.s)
Entspricht FileSize() - gibt die Dateigröße zurück, -2 für Ordner, -1 für nicht vorhanden.
BigFile::SaveFile(File.s,OutFile.s)
Speichert die Datei aus der Bigfile. Praktisch, wenn die Datei auf der Festplatte sein müssen. Ich empfehle dann aber den Temp-Ordner zum speichern.
Dateibefehle
Logischerweise kann man nur lesen. Wichtig: Da die Dateien gepackt sind, befindet sich nach den öffnen die Datei *vollständig* in Speicher.
BigFile::Open(file.s)
Gibt ein *handle zurück oder #false wenn ein Fehler aufgetreten ist.
BigFile::Close(*handle)
Schließt die Datei.
BigFile::EOB(*handle)
Entspricht EndOfFile EOF - hier halt EndOfBig
BigFile::LOB(*handle)
LengthOfFile LOF - hier EndOfBig
BigFile::POS(*handle)
Location LOC - hier Position
BigFile::Seek(*handle,pos,mode=#PB_Absolute)
Leseposition verschieben
BigFile::.s ReadS(*handle,flag=0,length.u=-1)
Vollständiger Nachbau von ReadString. Es werden die Flags #PB_Ascii,#PB_UTF8,#PB_Unicode und #PB_File_IgnoreEOL unterstützt. Bei UTF8 wird auch beachtet, das ein Zeichen mehrere Bytes groß sein können.
BigFile::ReadA(*handle)
BigFile::ReadB(*handle)
BigFile::ReadC(*handle)
BigFile::ReadD(*handle)
BigFile::ReadF(*handle)
BigFile::ReadI(*handle)
BigFile::ReadL(*handle)
BigFile::ReadQ(*handle)
BigFile::ReadU(*handle)
BigFile::ReadW(*handle)
Es wird der entsprechende Typ gelesen.
BigFile::ReadMem(*handle,*mem,len)
Entspricht ReadData(), es wird die Anzahl der gelesen Bytes zurückgegeben.
Catch*-Befehle
Die Routinen laden die entsprechende Datei in den Speicher, rufen den Catch-Befehl auf und gibt anschließend den Speicher wieder frei. Es wird jeweils das erzeugte Handle zurückgegeben oder halt #false, wenn es ein Problem gab.
BigFile::Image(id,file.s)
BigFile::JSON(id,file.s,flag=0)
BigFile::Music(id,file.s)
BigFile::Sound(id,file.s,flag=0)
BigFile::Sprite(id,file.s,flag=0)
Preference-Befehle
Ersatz für die Preference-Befehle. Auch hier kann man nur lesen und auch hier befindet sich die Datei solange im Speicher, bis man die Datei wieder schließt.
Ein großer Unterschied ist, das meine Befehle mit Handle arbeiten. Ansonsten sollten sich die Befehle identisch zu den Original-befehlen verhalten.
BigFile::OpenPref(file.s)
BigFile::ClosePref(*handle)
BigFile::ExaminePrefGroups(*handle)
BigFile::ExaminePrefKeys(*handle)
BigFile::NextPrefGroup(*handle)
BigFile::NextPrefKey(*handle)
BigFile::PrefGroup(*handle,name.s)
BigFile::PrefGroupName(*handle)
BigFile::PrefKeyName(*handle)
BigFile::PrefKeyValue(*handle)
BigFile::ReadPrefD(*handle,key.s,DefaultValue.d)
BigFile::ReadPrefF(*handle,key.s,DefaultValue.f)
BigFile::ReadPrefI(*handle,key.s,DefaultValue.i)
BigFile::ReadPrefL(*handle,key.s,DefaultValue.l)
BigFile::ReadPrefQ(*handle,key.s,DefaultValue.q)
BigFile::ReadPrefS(*handle,key.s,DefaultValue.s)
Einschränkungen
Dateien mit einer größe von Null können nicht eingefügt werden. Ich benutze die Größenangabe von Null als Ordnereintrag.
Desweiteren kann eine BigFile nur 2GB groß werden, da ich mit Absicht nur Longs für die Größenangaben und Positionsangaben benutze.
Da die Dateien ja gepackt vorhanden sind, ist das ganze natürlich Speicherlastig. Es muss zumindest kurzfristig sowohl die gepackte als auch ungepackte Datei in den Speicher gehalten werden muss. Sollte man also extrem große Dateien haben, empfiehlt es sich, diese eher nicht in eine BigFile zu packen.
Nachwort
Wie immer würde ich mich über Feedback, bugs oder sonstiges freuen. Ok, über Bugs nicht
Und nicht vergessen: Wenn der Debugger läuft, werden lokale Dateien bevorzugt.
Man könnte jetzt das ganze in eine Zip oder 7z-Datei einpflegen und mit den entsprechenden Packbefehle das ganze regeln. Problem hier, man müsste immer die Archivdatei aktuell halten, wenn man eine Änderung in den Dateien vornimmt oder man neue hinzufügen will, zum anderen kann die natürlich auch wieder jeder öffnen.
Mein BigFile-Modul soll diese Lücke schließen. Der Code ist so gestaltet, dass wenn eine Datei nicht in Bigfile gefunden wird, dass dann auf das normale Dateisystem ausgewichen wird. Auch gibt es einen Unterschied, ob man mit Debug compiled (meist wohl über Compile&run). Wenn der Debugger läuft, wird davon ausgegangen, das die Dateien in der BigFile veraltet sind und die Dateien aus den Dateisystem werden bevorzugt werden. Ohne Debugger haben die BigFile-Dateien immer Vorrang vor lokalen.
Es ist auch möglich mehrere BigFiles einzulesen. Sollten sich Überschneidungen ergeben, werden immer die Dateien aus der zuletzt eingelesen Bigfile bevorzugt. So kann man sehr einfach Patchfiles kreieren, ohne das man die alte Bigfile komplett ersetzen zu müssen.
Die Größe des Moduls erklärt sich dadurch, das ich im Prinzip die Directory, ReadFile und Preference Befehle nachgebaut habe. So kann man wie gewohnt programmieren, ohne sich zu kümmern, wo die Datei jetzt liegen - schon in einer Bigfile oder in lokale Dateien - es ist egal.
Wie wird eine Bigfile erstellt
Die Erstellungsbefehle werden normalerweise aus"CompilerIf". Sinn ist, das man die Erstellung eigentlich nur braucht, wenn man die BigFiles erstellt, nicht aber beim eigentlichen Programm. Um das Modul "BigFileCreate" zu benutzen, muss man vor den Include-Befehl die Konstante #bigfile_create auf #True setzen.
Code: Alles auswählen
#bigfile_create=#True
XIncludeFile "BigFile.pbi"
BigFileCreate::Create(file.s)
Erstellt und öffnet eine BigFile.
BigFileCreate::Close()
Schreibt die gepackte Indexinformation in die Datei und schließt sie. Damit ist die BigFile einsatzbereit.
BigFileCreate::AddDir(path.s,BigFileDir.s=".")
Fügt alle Dateien in angegeben Ordner und sämtliche Unterordner zur BigFile. Defaultmäßig werden sie in Hauptverzeichnis (".") hinzugefügt, kann man aber ändern.
BigFileCreate::AddFile(file.s,BigFileDir.s=".")
Fügt eine einzelne Datei dazu. Auch hier kann man den Unterordner in der BigFile angeben.
Wie greife ich auf eine BigFile zu?
Ob die Konstante #bigfile_create #True oder #False ist oder gar definiert ist, hat keine Bedeutung. Einfach nur die "BigFile.pbi" "includen" und fertig. Zugriff erfolgt über folgende Befehle:
BigFile::Load(file.s)
Lädt die Index-Daten der BigFile in den Speicher. Anschließend sind alle Dateien in der BigFile bekannt
BigFile::Catch(*mem)
Die BigFile kann auch schon in Speicher befinden. WICHTIG: Der Speicherbereich darf nicht freigegeben werden!
Das ganze funktioniert natürlich auch, wenn der Speicher sich in der DataSection mittels IncludeBinary sich befindet.
BigFile::Clear()
Löscht sämtliche Indexdateien. Wenn man auf eine BigFile zugreifen will, muss man sie anschließend neu laden.
BigFile::DebugList()
Nur mit Debugger verfügbar. In Debug-Fenster werden sämtliche Dateien ausgegeben, die in der BigFile sind.
BigFile::SetPath(path.s)
Wenn die Dateien nicht in der BigFile sind, werden sie CurrentDirectory() gesucht. Hier kann man einen anderen Pfad angeben.
Directory-Befehle
Die folgenden Befehle ersetzen die aus PureBasic bekannten Directory-Befehle. Benutzung ist identisch dazu. FinishExamine ist zwingend notwendig, sonst gibts ein Speicherleck.
Es werden sowohl die Dateien in BigFile als auch die in gesetzten Ordner berücksichtigt!
BigFile::Examine(path.s)
Gibt ein *handle zurück oder #False bei einen Fehler
BigFile::EntryType(*handle)
Gibt je nach Typ #PB_DirectoryEntry_Directory oder #PB_DirectoryEntry_File zurück.
BigFile::NextEntry(*handle)
#True, wenn es einen weiteren Eintrag gab, ansonsten #false.
BigFile::EntryIsBig(*handle)
#True, wenn es sich um eine Datei in der BigFile handelt oder #False, wenn es eine reale Datei ist.
BigFile::EntrySize(*handle)
Die Größe des Eintrags in Bytes.
BigFile::EntryName(*handle)
Gibt einen String mit den Namen zurück.
BigFile::FinishExamine(*handle)
Gibt das *handle wieder frei.
Dateisystembefehle
BigFile::Size(file.s)
Entspricht FileSize() - gibt die Dateigröße zurück, -2 für Ordner, -1 für nicht vorhanden.
BigFile::SaveFile(File.s,OutFile.s)
Speichert die Datei aus der Bigfile. Praktisch, wenn die Datei auf der Festplatte sein müssen. Ich empfehle dann aber den Temp-Ordner zum speichern.
Dateibefehle
Logischerweise kann man nur lesen. Wichtig: Da die Dateien gepackt sind, befindet sich nach den öffnen die Datei *vollständig* in Speicher.
BigFile::Open(file.s)
Gibt ein *handle zurück oder #false wenn ein Fehler aufgetreten ist.
BigFile::Close(*handle)
Schließt die Datei.
BigFile::EOB(*handle)
Entspricht EndOfFile EOF - hier halt EndOfBig
BigFile::LOB(*handle)
LengthOfFile LOF - hier EndOfBig
BigFile::POS(*handle)
Location LOC - hier Position
BigFile::Seek(*handle,pos,mode=#PB_Absolute)
Leseposition verschieben
BigFile::.s ReadS(*handle,flag=0,length.u=-1)
Vollständiger Nachbau von ReadString. Es werden die Flags #PB_Ascii,#PB_UTF8,#PB_Unicode und #PB_File_IgnoreEOL unterstützt. Bei UTF8 wird auch beachtet, das ein Zeichen mehrere Bytes groß sein können.
BigFile::ReadA(*handle)
BigFile::ReadB(*handle)
BigFile::ReadC(*handle)
BigFile::ReadD(*handle)
BigFile::ReadF(*handle)
BigFile::ReadI(*handle)
BigFile::ReadL(*handle)
BigFile::ReadQ(*handle)
BigFile::ReadU(*handle)
BigFile::ReadW(*handle)
Es wird der entsprechende Typ gelesen.
BigFile::ReadMem(*handle,*mem,len)
Entspricht ReadData(), es wird die Anzahl der gelesen Bytes zurückgegeben.
Catch*-Befehle
Die Routinen laden die entsprechende Datei in den Speicher, rufen den Catch-Befehl auf und gibt anschließend den Speicher wieder frei. Es wird jeweils das erzeugte Handle zurückgegeben oder halt #false, wenn es ein Problem gab.
BigFile::Image(id,file.s)
BigFile::JSON(id,file.s,flag=0)
BigFile::Music(id,file.s)
BigFile::Sound(id,file.s,flag=0)
BigFile::Sprite(id,file.s,flag=0)
Preference-Befehle
Ersatz für die Preference-Befehle. Auch hier kann man nur lesen und auch hier befindet sich die Datei solange im Speicher, bis man die Datei wieder schließt.
Ein großer Unterschied ist, das meine Befehle mit Handle arbeiten. Ansonsten sollten sich die Befehle identisch zu den Original-befehlen verhalten.
BigFile::OpenPref(file.s)
BigFile::ClosePref(*handle)
BigFile::ExaminePrefGroups(*handle)
BigFile::ExaminePrefKeys(*handle)
BigFile::NextPrefGroup(*handle)
BigFile::NextPrefKey(*handle)
BigFile::PrefGroup(*handle,name.s)
BigFile::PrefGroupName(*handle)
BigFile::PrefKeyName(*handle)
BigFile::PrefKeyValue(*handle)
BigFile::ReadPrefD(*handle,key.s,DefaultValue.d)
BigFile::ReadPrefF(*handle,key.s,DefaultValue.f)
BigFile::ReadPrefI(*handle,key.s,DefaultValue.i)
BigFile::ReadPrefL(*handle,key.s,DefaultValue.l)
BigFile::ReadPrefQ(*handle,key.s,DefaultValue.q)
BigFile::ReadPrefS(*handle,key.s,DefaultValue.s)
Einschränkungen
Dateien mit einer größe von Null können nicht eingefügt werden. Ich benutze die Größenangabe von Null als Ordnereintrag.
Desweiteren kann eine BigFile nur 2GB groß werden, da ich mit Absicht nur Longs für die Größenangaben und Positionsangaben benutze.
Da die Dateien ja gepackt vorhanden sind, ist das ganze natürlich Speicherlastig. Es muss zumindest kurzfristig sowohl die gepackte als auch ungepackte Datei in den Speicher gehalten werden muss. Sollte man also extrem große Dateien haben, empfiehlt es sich, diese eher nicht in eine BigFile zu packen.
Nachwort
Wie immer würde ich mich über Feedback, bugs oder sonstiges freuen. Ok, über Bugs nicht

Und nicht vergessen: Wenn der Debugger läuft, werden lokale Dateien bevorzugt.