File Handling Custom Type

Previous topic - Next topic

Slydog

I needed a break from my game, so I thought I'd contribute something.

Programming with so many languages, I hate having to learn each language's methods of doing things, such as file handling.
What I like to do is wrap those commands in a common wrapper, so my code appears similar across the different languages.

Here's my attempt at simplifying the file handling commands of GLB.
It only covers the basic types of file commands people need.

Code (glbasic) Select
CONSTANT FILE_MODE_APPEND = -1
CONSTANT FILE_MODE_WRITE = 0
CONSTANT FILE_MODE_READ = 1

CONSTANT EOF$ = "<EOF>"

TYPE TFile
fh% // File Handle
name$ // File Name
data$ // File Contents
eof% // File is at end?

// Reads entire file into 'self.data$'.  Closes file when finished.
// 'name$' > Optional.  Will use previously set 'self.name$' if blank.
// Returns > [TRUE if succeed | FALSE if fail]
FUNCTION Get%: name$=""
IF name$ <> "" THEN self.name$ = name$
self.data$ = ""
LOCAL line$ = ""
IF self.Open(FILE_MODE_READ)
WHILE ENDOFFILE(self.fh) = FALSE
READLINE self.fh, line$
INC self.data$, line$
WEND
CLOSEFILE self.fh
ELSE
LOG(">FIL:{TFile.Get$      } *** Error Getting File: [" + self.name$ + "] ***")
RETURN FALSE
ENDIF
self.Close()
RETURN TRUE
ENDFUNCTION

// Reads the next line in an already opened file.  Closes file when end is reached.
// Returns > [line data if succeed | 'EOF$' if fail]
FUNCTION GetLine$:
LOCAL line$
IF ENDOFFILE(self.fh)
line$ = EOF$
self.eof = TRUE
self.Close()
ELSE
self.eof = FALSE
READLINE self.fh, line$
ENDIF
RETURN line$
ENDFUNCTION

// Writes 'self.data$' to file
// 'name$' > Optional.  Will use previously set 'self.name$' if blank.
// Returns > [TRUE if succeed | FALSE if fail]
FUNCTION Write%: name$=""
IF name$ <> "" THEN self.name$ = name$
IF self.Open(FILE_MODE_WRITE)
WRITESTR self.fh, self.data$
ELSE
LOG(">FIL:{TFile.Write$    } *** Error Writting File: [" + self.name$ + "] ***")
RETURN FALSE
ENDIF
self.Close()
RETURN TRUE
ENDFUNCTION

// Appends string to end of file.
// 'extra$'> Data to append
// 'name$' > Optional.  Will use previously set 'self.name$' if blank.
// Returns > [TRUE if succeed | FALSE if fail]
FUNCTION Append%: extra$, name$=""
IF name$ <> "" THEN self.name$ = name$
IF self.Open(FILE_MODE_APPEND)
INC self.data$, extra$
WRITESTR self.fh, extra$
ELSE
LOG(">FIL:{TFile.Append$   } *** Error Appending File: [" + extra$ + "] ***")
RETURN FALSE
ENDIF
self.Close()
RETURN TRUE
ENDFUNCTION

// Opens file for procesing.
// 'mode%' > [FILE_MODE_APPEND | FILE_MODE_WRITE | FILE_MODE_READ]
// 'name$' > Optional.  Will use previously set 'self.name$' if blank.
// Returns > [TRUE if succeed | FALSE if fail]
FUNCTION Open%: mode%, name$=""
IF name$ <> "" THEN self.name$ = name$
self.fh = GENFILE()
self.eof = FALSE
IF OPENFILE(self.fh, self.name$, mode) = FALSE
LOG(">FIL:{TFile.Open      } *** Error Opening File: [" + self.name$ + "] ***")
self.eof = TRUE
RETURN FALSE
ENDIF
RETURN TRUE
ENDFUNCTION

// Closes file to further processing
FUNCTION Close%:
IF self.fh >= 0 THEN CLOSEFILE self.fh
self.fh = -1
self.eof = TRUE
ENDFUNCTION

// Checks if file exists
// Returns > [TRUE if exists | FALSE if doesn't]
FUNCTION Exists%:
RETURN DOESFILEEXIST(self.name$)
ENDFUNCTION

// Returns file name portion from a longer file path
// eg. For a 'self.name$' of [C:/Programs/GLBasic/glbasic.exe] will return [glbasic.exe]
// 'slash$' > Optional.  Folder Seperator.  Use if your path uses '\' instead. Uses '/' as default
FUNCTION GetName$: slash$="/"
LOCAL slash%
slash = REVINSTR(self.name$, slash$)
IF slash <=0 THEN RETURN self.name$
RETURN MID$(self.name$, slash+1)
ENDFUNCTION

ENDTYPE


FUNCTION LOG: message$, add_linefeed% = TRUE, show_time% = TRUE
STATIC datetime% = -1 // Log timer
LOCAL elapsed# // How much time has elapsed since program start (or manual reset)
IF datetime < 0 THEN datetime = GETTIMERALL() // Initialize log timer if not already set
IF message$ = "RESET" THEN datetime = GETTIMERALL() // Manually reset log timer
elapsed = (GETTIMERALL() - datetime) / 1000.0
?IFDEF WIN32
//?IFDEF GLB_DEBUG
//?ENDIF
IF show_time = TRUE THEN DEBUG "[" + FORMAT$(8, 3, elapsed) + "] "
DEBUG message$
IF add_linefeed THEN DEBUG "\n"
?ELSE
INLINE
STDOUT(message_Str);
ENDINLINE
?ENDIF
ENDFUNCTION


Here's some basic usages examples:

Code (glbasic) Select
LOCAL file AS TFile
LOCAL line$

// Read settings
IF NOT file.Get("settings.ini") THEN RETURN FALSE
DEBUG "Data:" + file.data$

// Write new settings data
file.data$ = "abcdefghijklmnopqrstuvwxyz"
file.Write("settings.ini")
file.Append("ZYXWVUTSR", "settings.ini"

// Optional method by setting file name directly
file.name$ = "scores.txt"
IF NOT file.Exists() THEN RETURN FALSE
file.data$ = "slydog | 12345"
file.Write()
file.Append("glbasic | 12346")

// Parsing a file line by line
IF NOT file.Open(FILE_MODE_READ, "graphics/fonts/comics.dat") THEN RETURN FALSE  // Exit and return 'FALSE' if can't open

REPEAT
    line$ = file.GetLine$()
    IF file.eof THEN GOTO SKIP // End of file  -> safe usage of 'GOTO' IMO :)
    IF LEN(line$) <= 0 THEN GOTO SKIP // Skip blank lines
    //  continue with parsing . . .
    SKIP:
UNTIL file.eof
file.Close()


The sample code wasn't tested, so no guarantees! 
And other commands weren't tested much either, so let me know if I have any bugs.
(or calls to my other functions I didn't include!)

I included my 'LOG()' command too as I find it useful for logging errors or for debugging code.
Also, it shows the time in ms since the program started so could be useful for identifying bottlenecks.
Here's a typical Output window for my game using this command:
Code (glbasic) Select
[   0.001] >GFX:{Sprite_Load     } fn:[Graphics/skin.png] | id:[1]
[   0.117] >GFX:{Sprite_Load     } fn:[Graphics/preview.png] | id:[3]
[   0.120] >GFX:{Sprite_Load     } fn:[Graphics/Fonts/blambot_secret_origins_32x50.png] | id:[4]
[   0.182] >GFX:{Sprite_Load     } *** File NOT FOUND!: [Graphics/Fonts/Kartika.png]
[   0.183] >FIL:{TFile.Open      } *** Error Opening File: [Graphics/Fonts/Kartika.dat] ***
[   0.184] >FON:{TGlyph.New%     } *** Error Opening Glyph File: [Graphics/Fonts/Kartika.dat] ***
[   0.186] >GFX:{Sprite_Load     } fn:[Graphics/Skins/Skin_Basic.png] | id:[5]
[   0.207] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[240,200,  0] | uv:[187,178]-[227,218]
[   0.234] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[  0,  0,255] | uv:[  0,192]-[ 48,240]
[   0.235] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[  0,  0,255] | uv:[ 48,192]-[ 96,240]
[   0.237] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[  0,  0,255] | uv:[ 96,192]-[144,240]
[   0.238] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[  0,  0,255] | uv:[144,192]-[186,240]
[   0.243] >GAM:{State           } STATE_MENU_MAIN
[   3.643] >GAM:{State           } STATE_MENU_WORLD
[   6.510] >GUI:{Scroll.IsScroll } FLICK Touch . . .
[   6.626] >GUI:{Scroll.IsScroll } FLICK Canceled: Finger Off
[   6.631] >GUI:{MenuWorld Select} id:[1000] | name:[Easy Peasy]
[   6.634] >GAM:{State           } STATE_MENU_LEVEL
[   6.728] >MAZ:{Maze_Generate   } >>>
[   6.742] >MAZ:{Maze_Generate   } <<< Done!
[   6.771] >GFX:{ShaderLoad      } fn:[/TwistedMaze/Window.app/Media/Graphics/Shaders/Maze]
[   6.887] >GFX:{ShaderLoad      } fn:[/TwistedMaze/Window.app/Media/Graphics/Shaders/Color]
[   8.770] >MAZ:{Maze_Generate   } >>>
[   8.777] >MAZ:{Maze_Generate   } <<< Done!
[   8.782] >GAM:{State           } STATE_PLAYING_MAZE
[   8.897] >GAM:{HudDraw         } stars:1
[  10.714] >GAM:{GLB_ON_QUIT     } Exiting >>>
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]