FileSystem Questions (get ready here's alot)

BlitzMax Forums/BlitzMax Beginners Area/FileSystem Questions (get ready here's alot)

Matt McFarland(Posted 2006) [#1]
OpenFile, do I use that if I create a file? or is the file assumed to be open?

Do i have to use openstream? What does open stream do and why is it a different command then openfile?

If I want to create a new file, do I use both openfile and openstream after I make it? or is the stream and file opened already?

If I want to write bytes to a file? Do I just use write bytes? Can I send variables like LastLevel,Score, etc into the file as bytes? Do they automatically know what is what when you open the file and read bytes?

Do you read bytes and THEN store the info? Or what? I'm so confused!

When you close the file does it change the data in the file? how do you re-write bytes if you have to? Do you jut use variables or is it some tricky position in file thing?

Thanks guys, I'm totally lost and I appreciate the help!!

If I know how to use these commands in the order they're used and use them right then I can open a few files for highscores and player data and configurations for the game. I'm assuming all I need to do is open the file then open a stream or not a stream and then load bytes some how (for next loop?) and then use the data to set variables in the game so its loaded or something. I dunno.. I'm lost :O - the problem is I dont know how it works.. like how does it know what byte to look at? can you store strings, integers, floats etc into 1 bytte? or do you have to use multiple bytes for what it is? if so then how do you know which bytes to load when youre reading the file? stuff like that.. ya know.. I'm just a little confused and need some clearing up!!

Thanks :)


Jim Teeuwen(Posted 2006) [#2]
A simple example for Highscore reading/writing:


Type THighscore

	Field List:TList
	
	Method New()
		List = New TList
	End Method
	
	'// Add a new Score entry
	Method Add( name:String, score:Int )
	
		'// Add the score
		List.AddLast( THighScoreEntry.Create( name, score ) )
		
		'// Sort the list.
		List.Sort(False)

	End Method
	
	Method Load( file:String )
	
		'// Does file exist?
		If( FileType( file ) = 0 ) Then
			Print "File " + file + " not found."
			Return
		End If
		
		'// Clear the list if it's allready filled.
		List.Clear()
	
		'// Create a new filestream to read from.
		Local fs:TStream = ReadFile( file )

		'// Loop through the file to get all the Highscore entries.
		'// We know that a name can be o longer than 30 characters, as
		'// we defined this in the THighScoreEntry.GetName() function.
		'// 'ReadString(fs, 30)' is the reason why. It requires a length
		'// to be specified.
		While( Not Eof( fs ) )
			Local name:String = ReadString(fs, 30)
			Local score:Int = ReadInt(fs)
			List.AddLast( THighScoreEntry.Create( name, score ) )
		Wend

		'// Close the filestream
		CloseFile( fs )
	
	End Method

	Method Save( file:String )
		'// No need to check if the file exists.
		'// We will simply create a new one if it doesn;t,
		'// else overwrite the old one.
		
		'// Create a stream to write to.
		Local fs:TStream = WriteFile( file )

		'// Loop through the Highscore entries and write
		'// them to the file
		For Local entry:THighScoreEntry = EachIn List
			WriteString( fs, entry.GetName() )
			WriteInt( fs, entry.GetScore() )
		Next
		
		'// Close the stream
		CloseFile( fs )
	End Method

End Type

Type THighScoreEntry
	Field _name:String
	Field _score:Int

	Function Create:THighScoreEntry( name:String, score:Int )
		Local e:THighScoreEntry = New THighScoreEntry
		e._name = name.Trim()
		e._score = score
		Return e
	End Function

	Method GetName:String()
		'// For saving purposes, our Name property
		'// Must have a Fixed length. We will assume a maximum
		'// String size of 30 characters. If _name is less than 30
		'// in length, we will Pad it with blank spaces.
		Local tmp:String = _name
		While( tmp.length < 30 )
			tmp = tmp + " "
		Wend
		Return tmp
	End Method
	Method GetScore:Int()
		Return _score
	End Method
End Type


'// Demo.
Local highscore:THighscore = New THighscore

'// Create some new Highscore entries.
highscore.Add( "Bob", 1000)
highscore.Add( "Joe", 2000)
highscore.Add( "Jack", 3000)
highscore.Add( "Bill", 4000)
highscore.Add( "Banana", 5000)
highscore.Add( "Pie", 6000)

'// Save the scores to file.
highscore.Save( "c:\temp\test.txt" )

'// Ad some point we can load the scores from the file again.
highscore.Load( "c:\temp\test.txt" )

Local index:Int = 1
For Local entry:THighScoreEntry = EachIn highscore.List
	Print( index + ". " + entry.GetName() + entry.GetScore() )
	index = index + 1
Next




Yan(Posted 2006) [#3]
OpenFile(), CloseFile() and the like are just helper functions for backwards compatibility with previous BB incarnations and they mostly just wrap the stream commands. OpenFile() is slighlty different to OpenStream(), however, and will pull a file from a non seekable stream (http::, tcp::, etc) into a bank and stream it from there to make it seekable. Just use the commands you feel more comfortable with.

OpenFile()/OpenStream()/TStream.open() will expect a file to exist if you set 'readable' to be true. If you set *only* 'writeable' to be true, then a file will be created if it doesn't already exist.

When writing to a file, you can freely mix the type of variables you write. You are responsible for making sure you read them back in the right order, however.
For instance, if you write a file out like so...
...ETC...
myStream.WriteByte(lives@)
myStream.WriteFloat(posX#)
myStream.WriteInt(score%)
...ETC...
You must read it back in the same order...
...ETC...
lives@ = myStream.ReadByte()
posX# = myStream.ReadFloat()
score% = myStream.ReadInt()
...ETC...


Every time you close and re-open a file, the file pointer points to the start of the file. If you want to append anything to that file (as opposed to overwriting it), you need to seek to the end of the file first...
Local myStream:TStream = OpenStream("myfile.dat", true, true)
If myStream = Null Then myStream:TStream = OpenStream("myfile.dat", false, true)
myStream.Seek(myStream.Size())
myStream.WriteByte(42)
myStream.Close()



Matt McFarland(Posted 2006) [#4]
Thanks guys.. I would have got back sooner but I got hit with a nasty flu (I'm recovering today, finally)

@ Defiance: I REALLY really reallly appreciate your help :), but your code example isn't as helpful as someone just explaining how this stuff works!!

@ Ian, why did you WriteByte(42)? also, I dont see which line of code (unless seek is it?) that appends it to the end of file. Or is the seek command moving it to the end of file, and then you're writing a byte '42' to the end of file?

So I dont need to open the file ever? just open stream!! I write the files in bmx.. so it can be like this

float,float,byte,byte,int,int,float,byte

and I just read the file the same way..

float,float,byte,byte,int,int,float,byte


Sounds easy enough.. So I can use streams and not open / close? because open and close are read only things and I dont need that right?

Thanks,
Matt


Matt McFarland(Posted 2006) [#5]
So when I load a file, I can just do an open stream and then pull all the data 1 by 1 in the exact order it was saved? That makes it seem too easy!! :-O


Qweeg(Posted 2006) [#6]
Hiya Matt,

Can you not just store data in a file line by line. Then read each line of data into either an array (for high scores) or directly into variables for player data (you need to cast the data into the correct type sometimes).

Here are examples of how I have done it (drop me a line if you want to go through something in more detail).
HIGH SCORES
Global fleHighScore:TStream = ReadFile("HiScores\HighScores.txt")
If not fleHighScore RuntimeError "could not open file HighScores.txt"

Global arrHighScore:String[20]
Local i:Int = 0

While not Eof(fleHighScore)
	arrHighScore[i] = ReadLine(fleHighScore)
	i:+1
Wend

CloseStream fleHighScore



the above code just opens a file for reading and then reads it line by line into an array. As long as the scores are in the file in order on seperate lines this will be fine. Then at the end you can compare the player's score with the entries in the array to see if they need to be on the high score. then you write back the entire array to the high score table like this:

Function WriteHighScores()

	fleHighScore = WriteStream("HiScores\HighScores.txt")

	If not fleHighScore RuntimeError "Failed to open a WriteStream to file HighScores.txt"

	For Local strHighScoreString:String = EachIn arrHighScore
		WriteLine fleHighScore, strHighScoreString
	Next

	CloseStream fleHighScore

EndFunction



Okay for player attributes I use the same idea but read the file line by line into the appropriate attributes. The thing is here you need to know what order things appear in the file (but I just keep a control file). So something like this:
PLAYER ATTRIBUTES
	Method Initialise(mintPlayerNumber:Int)
	
		Local flePlayerProfile:TStream	
		'Read the player profile data in from a text file
		Select mintPlayerNumber
		
		Case 0

			
			flePlayerProfile = ReadFile("Profiles\PlayerOne.txt")
			If not flePlayerProfile Then RuntimeError "could not read Player Two Profile"
			
		Case 1
		
			
			flePlayerProfile = ReadFile("Profiles\PlayerTwo.txt")
			If not flePlayerProfile Then RuntimeError "could not read Player Two Profile"
			
		End Select

		Self.imgPlayerText = LoadImage("incbin::Images\Text\"+ReadLine(flePlayerProfile))
		Self.imgPlayerWins = LoadImage("incbin::Images\Text\"+ReadLine(flePlayerProfile))
		Self.imgAvatar = LoadImage("incbin::Images\Players\"+ReadLine(flePlayerProfile))
		Self.strNickname = ReadLine(flePlayerProfile)
		Self.varPower = Float(ReadLine(flePlayerProfile))
		Self.varSpin = Float(ReadLine(flePlayerProfile))
		Self.varCueing = Float(ReadLine(flePlayerProfile))
		Self.varPoise = Float(ReadLine(flePlayerProfile))
		fleCueProfile = ReadFile("Profiles\"+ReadLine(flePlayerProfile))
		If not fleCueProfile Then RuntimeError "could not read Player Cue Profile"
		
		CloseStream flePlayerProfile
		
	EndMethod



basically I just declare a Tstream and load a file into it (using readfile) then read it back a line at a time into variable or arrays (using readline)

Seems to work for me.

Hope this helps Matt
Tim


Yan(Posted 2006) [#7]
Sorry, I should have commented that code...

Local myStream:TStream = OpenStream("myfile.dat", true, true) ' Try to open a file for read/write (because readable is true, it must exist)

If myStream = Null Then myStream:TStream = OpenStream("myfile.dat", false, true)'The file doesn't exist so create one.

myStream.Seek(myStream.Size())'seek to the end of the file (if the file isn't readable, size() will return 0).

myStream.WriteByte(42)'Add the meaning of life (Or an asterisk, if you prefer) to the end of the file (or start if it's a new file).

myStream.Close()'close the file.


So I dont need to open the file ever? just open stream!!
Well they're pretty much the same thing when you dealing with files.


Chris C(Posted 2006) [#8]
rather that using a RAW format for your file you might want to consider xml (you can always password zip if ;) ) or even
bung the data in an sqlite database file

Your custom file format can soon get out of hand...


Matt McFarland(Posted 2006) [#9]
So whenever it reads something inside a file, it automatically moves it's glasses / pointer / eyeballs (whatever its called) to the next line so then when you use a read command again it starts after the last point it read at? if thats true then how do you skip data? or how do you go back in data? it reminds me of a VCR tape reader without the fastforward / rewind button lol - good thing is we really only need to parse through the data once and save it all correctly.

I dont know xml so bahh on that :-P

I'm pretty sure if I iterate all the variables it shouldnt be hard to store data. riiight guyss???


Yan(Posted 2006) [#10]
As long as you don't close the file (or move the pointer using seek), yes.


Matt McFarland(Posted 2006) [#11]
Thanks :) You've been helpful today Ian. And thank you Tim for the line-by-line thing but I'm going to go for the fancy readbyte/float/int/string stuff :)

I feel ready to go!!
lets recap!

WHEN WRITING DATA
Open Stream
If file doesnt exist then make one
Save everything in the same order which it will be loaded
Close Stream


***Old-School Audio-Record Ripping Sound Effect ***
Ok I dont understand this.. if it's overwriting data can't it get messy and totally ruin the file?


WHEN LOADING DATA
Open Stream
Read data in the same order its been written and apply the data to variables
Close Stream



Yan(Posted 2006) [#12]
Ok I don't understand this.. if it's overwriting data can't it get messy and totally ruin the file?
It can do if you're seeking all over the place, overwriting things here and there. With config and hi score files, you're generally just reading or writing the whole file at once so it's pretty straight forward.


Matt McFarland(Posted 2006) [#13]
If you rewrite the whole file then do you delete the old file then remake it with the new vars?


tonyg(Posted 2006) [#14]
The EOF for the file is rewritten each time you write to it.
If you have a 3 line file eof is, for example +3.
If you then write 1 line the EOF is +1 and the other two lines are no longer part of the file.
Is that what you're asking?


Matt McFarland(Posted 2006) [#15]
Actually what I am asking is if I want to over-write the file, do I have to delete it first? or simply set the read switch to false?


tonyg(Posted 2006) [#16]
You don't have to delete it just write to it.
In fact, this doesn't look right...
' writefile.bmx

file:TStream=WriteFile("test.txt")

If Not file RuntimeError "failed to open test.txt file" 

WriteLine file,"hello world"

CloseStream file

file:TStream=OpenFile("test.txt")
WriteLine file,"BYE BYE!"
CloseStream file

while this...
' writefile.bmx

file:TStream=WriteFile("test.txt")

If Not file RuntimeError "failed to open test.txt file" 

WriteLine file,"hello world"

CloseStream file

file:TStream=WriteFile("test.txt")
WriteLine file,"BYE BYE!"
CloseStream file

works as expected (e.g. doesn't leave a residual 'd')


Yan(Posted 2006) [#17]
or simply set the read switch to false?
Yes (as tonyg points out).


Matt McFarland(Posted 2006) [#18]
Thanks guys you've pretty much cleared up all my confusion dealing with the filesystem commands. I'm now ready to use them with confidence!! :)


Matt McFarland(Posted 2006) [#19]
ACTUALLY,
Local myStream:TStream = OpenStream("myfile.dat", true, true) ' Try to open a file for read/write (because readable is true, it must exist)

If myStream = Null Then myStream:TStream = OpenStream("myfile.dat", false, true)'The file doesn't exist so create one.



If myfile.dat does not exist it crashes and says there is a memory exception error. Anyway to make it ignore that and just make the file like your code suggets?


Blitzplotter(Posted 2006) [#20]
@tonyg... thx for your post . I successfully ran your snippet of code as a .bmx . I tried incorporating it into a Game.Types file with limited success (it did not compile). I had the following message reported to me upon trying to compile:

'Compile Error Identifier 'file' not found'
(relating to:


file:TSTream=Writefile("test.txt")



Appreciate I'm 'bumping' this thread..., and it's only 3 months young... but I'm a noob to Bmax and need all the help I can get.... Regards...


H&K(Posted 2006) [#21]
I think this is a scope thing. put a scope identifier on file.
(this is a total guess, so sorry if its wrong)
EDIT.


Stratch what I said. This works on mine. Which one where you running?


assari(Posted 2006) [#22]
Are you using strict?
if yes you need to define the variable file e.g.
Local file:TStream=WriteFile("test.txt")



Blitzplotter(Posted 2006) [#23]
Cheers assari, 'Local' prior to file resolved my compiling issue... my project marches ever onwards.........