view: render map entities
[d2df-maped.git] / src / vfs / common.nim
blob824787dc10e47781858a9182862f3213db39427c
1 import
2   std/streams, std/tables, std/os, std/strutils,
3   ../utils
5 type
6   FsFileEntry* = ref object of RootObj
7     parent*: FsArchive
8     path*: string
9     data*: string # if this is "". file is yet to be read
10     dataOfs*: int # if this is -1, file is yet to be written
11     dataSize*: int
12     compressed*: bool
13     uncompressedSize*: int # if this is -1, length is unknown
15   FsArchive* = ref object of RootObj
16     name*: string
17     path*: string
18     files*: Table[string, FsFileEntry]
19     stream*: Stream
20     writeable*: bool
21     existing*: bool
22     changed*: bool
24   FsStreamObj* = object of StringStreamObj
25     entry*: FsFileEntry
26     writing*: bool
27   FsStream* = ref FsStreamObj
29   FsFormatError* = object of IOError
30   FsReadError* = object of IOError
31   FsWriteError* = object of IOError
33 method open*(self: FsArchive) {.base.} =
34   raiseBaseMethodCall("open")
36 method save*(self: FsArchive) {.base.} =
37   raiseBaseMethodCall("save")
39 method loadFile*(self: FsArchive, f: FsFileEntry) {.base.} =
40   raiseBaseMethodCall("loadFile")
42 method close*(self: FsArchive) {.base.} =
43   if self.writeable and self.changed:
44     # read the contents of all already existing files
45     for f in self.files.mvalues():
46       if f.data == "":
47         self.loadFile(f)
48     # if operating on a file, make a backup and truncate it
49     if self.path != "":
50       self.stream.close()
51       try:
52         copyFile(self.path, self.path & ".bak")
53       except OSError:
54         discard
55       self.stream = newFileStream(self.path, fmWrite)
56     # write the new file
57     self.save()
58     self.changed = false
59   self.files.clear()
60   if self.path != "" and self.stream != nil:
61     self.stream.close()
62   self.stream = nil
64 proc newFsFileEntry*(archive: FsArchive, path: string): FsFileEntry =
65   result = new(FsFileEntry)
66   result.parent = archive
67   result.path = path
68   result.uncompressedSize = -1
69   result.dataOfs = -1
70   result.data = ""
72 proc fsClose(s: Stream) =
73   var f = FsStream(s)
74   var ss = StringStream(s)
75   if f.writing:
76     f.entry.data = ss.data
77     f.entry.dataOfs = -1 # need rewrite
78     if f.entry.compressed:
79       f.entry.dataSize = -1 # need compression
80     else:
81       f.entry.dataSize = ss.data.len()
82     f.entry.uncompressedSize = ss.data.len()
83     f.entry.parent.files[f.entry.path.toUpperAscii()] = f.entry
84   else:
85     f.entry.data = "" # don't leave that shit cached
86   ss.data = ""
88 proc newFsStream*(f: FsFileEntry, write: bool): FsStream =
89   let data = f.data
90   result = new(FsStream)
91   result.writing = write
92   result.entry = f
93   # these are not really made to inherit from them
94   var ss = newStringStream(data)
95   result.data = ss.data
96   result.closeImpl = fsClose # our own proc
97   result.atEndImpl = ss.atEndImpl
98   result.setPositionImpl = ss.setPositionImpl
99   result.getPositionImpl = ss.getPositionImpl
100   result.readDataStrImpl = ss.readDataStrImpl
101   result.readLineImpl = ss.readLineImpl
102   result.readDataImpl = ss.readDataImpl
103   result.peekDataImpl = ss.peekDataImpl
104   result.writeDataImpl = ss.writeDataImpl
105   result.flushImpl = ss.flushImpl
107 proc findFile*(self: FsArchive, localPath: string): FsFileEntry =
108   let upperPath = localPath.toUpperAscii()
109   if upperPath in self.files:
110     return self.files[upperPath]
111   result = nil
113 proc loadFile*(self: FsArchive, localPath: string) =
114   var f = self.findFile(localPath)
115   if f == nil:
116     raise newException(FsReadError, "file " & localPath & " does not exist in " & self.name)
117   self.loadFile(f)
119 proc writeFile*(self: FsArchive, localPath: string, compressed: bool = true): Stream =
120   if not self.writeable:
121     raise newException(FsWriteError, self.name & " is not writeable")
122   var f = self.findFile(localPath)
123   if f == nil:
124     f = self.newFsFileEntry(localPath.toUpperAscii())
125     f.compressed = compressed
126   else:
127     self.loadFile(f)
128   result = newFsStream(f, true)
130 proc readFile*(self: FsArchive, localPath: string): Stream =
131   var f = self.findFile(localPath)
132   if f == nil:
133     return nil
134   self.loadFile(f)
135   result = newFsStream(f, false)