Skip to content

Commit

Permalink
Various files API improvements
Browse files Browse the repository at this point in the history
* Add file tests, fix file exists, fix write length 0

* Centralize file error handling

* Add proc for writing a string
  • Loading branch information
Nycto authored and ninovanhooff committed Apr 4, 2024
1 parent 6dbc789 commit 20d638b
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 53 deletions.
89 changes: 37 additions & 52 deletions src/playdate/file.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,19 @@ type
SDFileObj {.requiresinit.} = object
resource: SDFilePtr
path: string
SDFile* = ref SDFileObj
SDFile* = ref SDFileObj

proc requireValidStatus(res: SomeInteger): int {.raises: [IOError], discardable.} =
privateAccess(PlaydateFile)
if res < 0:
raise newException(IOError, $playdate.file.geterr())
return res.int

proc requireNotNil[T: pointer](res: T): T {.raises: [IOError].} =
privateAccess(PlaydateFile)
if res == nil:
raise newException(IOError, $playdate.file.geterr())
return res

proc `=destroy`(this: var SDFileObj) =
privateAccess(PlaydateFile)
Expand All @@ -29,105 +41,78 @@ proc fileCallback(filename: ConstChar, userdata: pointer) {.cdecl.} =
proc listFiles*(this: ptr PlaydateFile, path: string, showHidden: bool = false): seq[string] {.raises: [IOError]} =
privateAccess(PlaydateFile)
var files = newSeq[string]()
var res = this.listfiles(toC(path.cstring), fileCallback, addr(files), if showHidden: 1 else: 0)
if res != 0:
raise newException(IOError, $playdate.file.geterr())
this.listfiles(toC(path.cstring), fileCallback, addr(files), if showHidden: 1 else: 0).requireValidStatus
return files

proc stat*(this: ptr PlaydateFile, path: string): FileStat {.raises: [IOError]} =
privateAccess(PlaydateFile)
var info: FileStat = FileStat()
let res = this.stat(path.cstring, addr(info[]))
if res != 0:
raise newException(IOError, $playdate.file.geterr())
this.stat(path.cstring, addr(info[])).requireValidStatus
return info

proc exists*(this: ptr PlaydateFile, path: string): bool =
privateAccess(PlaydateFile)
var info: FileStat = FileStat()
let res = this.stat(path.cstring, addr(info[]))
return res != 0
var info: FileStatRaw
return this.stat(path.cstring, addr(info)) == 0

proc unlink*(this: ptr PlaydateFile, path: string, recursive: bool) {.raises: [IOError]} =
privateAccess(PlaydateFile)
let res = this.unlink(path.cstring, if recursive: 1 else: 0)
if res != 0:
raise newException(IOError, $playdate.file.geterr())
this.unlink(path.cstring, if recursive: 1 else: 0).requireValidStatus

proc mkdir*(this: ptr PlaydateFile, path: string) {.raises: [IOError]} =
privateAccess(PlaydateFile)
let res = this.mkdir(path.cstring)
if res != 0:
raise newException(IOError, $playdate.file.geterr())
this.mkdir(path.cstring).requireValidStatus

proc rename*(this: ptr PlaydateFile, fromName: string, to: string) {.raises: [IOError]} =
privateAccess(PlaydateFile)
let res = this.rename(fromName.cstring, to.cstring)
if res != 0:
raise newException(IOError, $playdate.file.geterr())
this.rename(fromName.cstring, to.cstring).requireValidStatus

proc close*(this: SDFile) {.raises: [IOError]} =
privateAccess(PlaydateFile)
let res = playdate.file.close(this.resource)
if res != 0:
raise newException(IOError, $playdate.file.geterr())
discard playdate.file.close(this.resource).requireValidStatus

proc flush*(this: SDFile): int {.raises: [IOError]} =
proc flush*(this: SDFile): int {.raises: [IOError], discardable} =
privateAccess(PlaydateFile)
let res = playdate.file.flush(this.resource)
if res < 0:
raise newException(IOError, $playdate.file.geterr())
return res
return playdate.file.flush(this.resource).requireValidStatus

proc open*(this: ptr PlaydateFile, path: string, mode: FileOptions): SDFile {.raises: [IOError]} =
privateAccess(PlaydateFile)
let res = this.open(path.cstring, mode)
if res == nil:
raise newException(IOError, $playdate.file.geterr())
return SDFile(resource: res, path: path)
return SDFile(resource: this.open(path.cstring, mode).requireNotNil, path: path)

proc read*(this: SDFile, length: uint): tuple[bytes: seq[byte], length: int] {.raises: [IOError]} =
privateAccess(PlaydateFile)
var buffer = newSeq[byte](length)
let res = playdate.file.read(this.resource, addr(buffer[0]), length.cuint)
if res < 0:
raise newException(IOError, $playdate.file.geterr())
let res = playdate.file.read(this.resource, addr(buffer[0]), length.cuint).requireValidStatus
return (bytes: buffer, length: res.int)

proc read*(this: SDFile): seq[byte] {.raises: [IOError]} =
let size = playdate.file.stat(this.path).size
privateAccess(PlaydateFile)
var buffer = newSeq[byte](size)
let res = playdate.file.read(this.resource, addr(buffer[0]), size.cuint)
if res < 0:
raise newException(IOError, $playdate.file.geterr())
playdate.file.read(this.resource, addr(buffer[0]), size.cuint).requireValidStatus
return buffer

proc readString*(this: SDFile): string {.raises: [IOError].} =
let size = playdate.file.stat(this.path).size
privateAccess(PlaydateFile)
var str = newString(size)
let res = playdate.file.read(this.resource, addr(str[0]), size.cuint)
if res < 0:
raise newException(IOError, $playdate.file.geterr())
playdate.file.read(this.resource, addr(str[0]), size.cuint).requireValidStatus
return str

proc seek*(this: SDFile, pos: int, whence: int) {.raises: [IOError]} =
privateAccess(PlaydateFile)
let res = playdate.file.seek(this.resource, pos.cint, whence.cint)
if res != 0:
raise newException(IOError, $playdate.file.geterr())
playdate.file.seek(this.resource, pos.cint, whence.cint).requireValidStatus

proc tell*(this: SDFile): int {.raises: [IOError]} =
privateAccess(PlaydateFile)
let res = playdate.file.tell(this.resource)
if res < 0:
raise newException(IOError, $playdate.file.geterr())
return res
return playdate.file.tell(this.resource).requireValidStatus

proc write*(this: SDFile, buffer: seq[byte], length: uint): int {.raises: [IOError]} =
proc write*(this: SDFile, buffer: seq[byte], length: uint): int {.raises: [IOError], discardable} =
privateAccess(PlaydateFile)
let res = playdate.file.write(this.resource, unsafeAddr(buffer[0]), length.cuint)
if res < 0:
raise newException(IOError, $playdate.file.geterr())
return res
if length > 0:
return playdate.file.write(this.resource, unsafeAddr(buffer[0]), length.cuint).requireValidStatus

proc write*(this: SDFile, content: string): int {.raises: [IOError], discardable} =
privateAccess(PlaydateFile)
if content.len > 0:
return playdate.file.write(this.resource, unsafeAddr(content[0]), content.len.cuint).requireValidStatus
3 changes: 2 additions & 1 deletion tests/src/playdate_tests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
##

import playdate/api
import ../[t_buttons, t_graphics, t_nineslice]
import ../[t_buttons, t_graphics, t_nineslice, t_files]

proc runTests() {.raises: [].} =
try:
execButtonsTests()
execGraphicsTests(true)
execNineSliceTests(true)
execFilesTest()
except Exception as e:
quit(e.msg & "\n" & e.getStackTrace)

Expand Down
51 changes: 51 additions & 0 deletions tests/t_files.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import unittest, playdate/api

proc createFile(name: string, body: string = "") =
var handle = playdate.file.open(name, kFileWrite)
check(handle.write(cast[seq[byte]](body), body.len.uint) >= 0)

proc execFilesTest*() =
suite "File loading":
test "Writing and reading files":
createFile("test_data.txt", "foo")
var handle = playdate.file.open("test_data.txt", kFileReadData)
check(handle.readString() == "foo")

test "Writing strings to files":
block:
playdate.file.open("test_data.txt", kFileWrite).write("file content")
check(playdate.file.open("test_data.txt", kFileReadData).readString() == "file content")

test "Listing files":
createFile("list_files.txt")
check("list_files.txt" in playdate.file.listFiles("/"))

test "Stating missing file":
expect IOError:
discard playdate.file.stat("not_real.txt")

test "Stating existing file":
createFile("stat_file.txt", "some content")
let stat = playdate.file.stat("stat_file.txt")
check(stat.isdir == 0)
check(stat.size == 12)

test "Checking if files exists":
check(playdate.file.exists("not_a_file.txt") == false)

createFile("real_file.txt")
check(playdate.file.exists("real_file.txt"))

test "Unlinking files":
createFile("delete_me.txt")
playdate.file.unlink("delete_me.txt", false)
check(playdate.file.exists("delete_me.txt") == false)

test "mkdir":
playdate.file.mkdir("my_dir")
check(playdate.file.stat("my_dir").isdir == 1)

test "Renaming file":
createFile("original_file.txt")
playdate.file.rename("original_file.txt", "renamed_file.txt")
check(playdate.file.exists("renamed_file.txt"))

0 comments on commit 20d638b

Please sign in to comment.