finish move to Python-handled imports
[Tsunagari.git] / src / reader.cpp
blobeff4accc05ea153160cb5d29d384684b0b0eaa9e
1 /***************************************
2 ** Tsunagari Tile Engine **
3 ** reader.cpp **
4 ** Copyright 2011-2013 PariahSoft LLC **
5 ***************************************/
7 // **********
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 // **********
27 #include <errno.h>
28 #include <stdlib.h>
30 #include <boost/scoped_ptr.hpp>
31 #include <boost/shared_ptr.hpp>
32 #include <Gosu/Bitmap.hpp>
33 #include <Gosu/Image.hpp>
34 #include <Gosu/IO.hpp>
35 #include <map>
36 #include <physfs.h>
38 #include "cache-template.cpp"
39 #include "client-conf.h"
40 #include "formatter.h"
41 #include "log.h"
42 #include "python.h"
43 #include "python-bindings-template.cpp"
44 #include "reader.h"
45 #include "script.h"
46 #include "window.h"
47 #include "xml.h"
49 #define ASSERT(x) if (!(x)) { return false; }
51 typedef boost::shared_ptr<xmlDtd> DTDRef;
52 typedef boost::shared_ptr<std::string> StringRef;
55 // Caches that store processed, game-ready objects. Garbage collected.
56 Cache<ImageRef> images;
57 Cache<TiledImageRef> tiles;
58 Cache<SampleRef> sounds;
59 Cache<XMLRef> xmls;
60 Cache<StringRef> texts;
62 // DTDs don't expire. No garbage collection.
63 typedef std::map<std::string, DTDRef> DTDMap;
64 DTDMap dtds;
68 static std::string path(const std::string& entryName)
70 // XXX: archive might not be world
71 return conf.worldFilename + "/" + entryName;
74 template <class T>
75 static bool readFromDisk(const std::string& name, T& buf)
77 PHYSFS_sint64 size;
78 PHYSFS_File* zf;
80 if (!PHYSFS_exists(name.c_str())) {
81 Log::err("Reader", Formatter("%: file missing")
82 % path(name));
83 return false;
86 zf = PHYSFS_openRead(name.c_str());
87 if (!zf) {
88 Log::err("Reader", Formatter("%: error opening file: %")
89 % path(name) % PHYSFS_getLastError());
90 return false;
93 size = PHYSFS_fileLength(zf);
94 if (size == -1) {
95 Log::err("Reader", Formatter("%: could not determine file size: %")
96 % path(name) % PHYSFS_getLastError());
97 PHYSFS_close(zf);
98 return false;
100 if (size > std::numeric_limits<uint32_t>::max()) {
101 // FIXME: Technically, we just need to issue multiple calls to
102 // PHYSFS_read. Fix when needed.
103 Log::err("Reader", Formatter("%: file too long (>4GB)")
104 % path(name));
105 PHYSFS_close(zf);
106 return false;
109 buf.resize(size);
110 if (size == 0) {
111 PHYSFS_close(zf);
112 return true;
115 if (PHYSFS_read(zf, (char*)(buf.data()),
116 (PHYSFS_uint32)size, 1) != 1) {
117 Log::err("Reader", Formatter("%: error reading file: %")
118 % path(name) % PHYSFS_getLastError());
119 PHYSFS_close(zf);
120 return false;
123 PHYSFS_close(zf);
124 return true;
127 // FIXME: Should be moved to xml.cpp!!!!!!
128 static DTDRef parseDTD(const std::string& path)
130 xmlCharEncoding enc = XML_CHAR_ENCODING_NONE;
132 std::string bytes = Reader::readString(path);
133 if (bytes.empty())
134 return DTDRef();
136 xmlParserInputBuffer* input = xmlParserInputBufferCreateMem(
137 bytes.c_str(), (int)bytes.size(), enc);
138 if (!input)
139 return DTDRef();
141 xmlDtd* dtd = xmlIOParseDTD(NULL, input, enc);
142 if (!dtd)
143 return DTDRef();
145 return DTDRef(dtd, xmlFreeDtd);
148 // FIXME: Should be moved to xml.cpp!!!!!!
149 static bool preloadDTDs()
151 ASSERT(dtds["dtd/area.dtd"] = parseDTD("dtd/area.dtd"));
152 ASSERT(dtds["dtd/entity.dtd"] = parseDTD("dtd/entity.dtd"));
153 ASSERT(dtds["dtd/tsx.dtd"] = parseDTD("dtd/tsx.dtd"));
154 ASSERT(dtds["dtd/world.dtd"] = parseDTD("dtd/world.dtd"));
155 return true;
158 static xmlDtd* getDTD(const std::string& name)
160 DTDMap::iterator it = dtds.find(name);
161 return it == dtds.end() ? NULL : it->second.get();
164 static XMLDoc* readXMLDoc(const std::string& name,
165 const std::string& dtdPath)
167 std::string p = path(name);
168 std::string data = Reader::readString(name);
169 xmlDtd* dtd = getDTD(dtdPath);
171 if (!dtd || data.empty())
172 return NULL;
173 XMLDoc* doc = new XMLDoc;
174 if (!doc->init(p, data, dtd)) {
175 delete doc;
176 return NULL;
178 return doc;
181 static bool callInitpy(const std::string& archivePath)
183 ASSERT(Reader::prependPath(archivePath));
184 bool exists = Reader::resourceExists("__init__.py");
185 Log::info(archivePath,
186 std::string("__init__.py: ") +
187 (exists ? "found" : "not found"));
188 if (exists)
189 // FIXME: Python will cache the __init__ module with no path prefix
190 ASSERT(Script::create("__init__"));
191 ASSERT(Reader::rmPath(archivePath));
192 return true;
205 bool Reader::init(char* argv0)
207 ASSERT(PHYSFS_init(argv0) != 0);
209 // If any of our archives contain a file called "__init__.py", call it.
210 for (Conf::StringVector::const_iterator it = conf.dataPath.begin(); it != conf.dataPath.end(); it++) {
211 const std::string archive = *it;
212 ASSERT(callInitpy(archive));
214 ASSERT(callInitpy(BASE_ZIP_PATH));
216 ASSERT(prependPath(BASE_ZIP_PATH));
218 // DTDs must be loaded from BASE_ZIP. They cannot be allowed to be
219 // loaded from the world.
220 ASSERT(preloadDTDs());
222 ASSERT(prependPath(conf.worldFilename));
223 for (Conf::StringVector::const_iterator it = conf.dataPath.begin(); it != conf.dataPath.end(); it++) {
224 const std::string archive = *it;
225 ASSERT(prependPath(archive));
228 return true;
231 void Reader::deinit()
233 PHYSFS_deinit();
236 bool Reader::prependPath(const std::string& path)
238 int err = PHYSFS_mount(path.c_str(), NULL, 0);
239 if (err == 0) {
240 Log::fatal("Reader", Formatter("%: could not open archive: %")
241 % path % PHYSFS_getLastError());
242 return false;
245 pythonPrependPath(path);
247 return true;
250 bool Reader::appendPath(const std::string& path)
252 int err = PHYSFS_mount(path.c_str(), NULL, 1);
253 if (err == 0) {
254 Log::fatal("Reader", Formatter("%: could not open archive: %")
255 % path % PHYSFS_getLastError());
256 return false;
259 pythonRmPath(path);
261 return true;
264 bool Reader::rmPath(const std::string& path)
266 int err = PHYSFS_removeFromSearchPath(path.c_str());
267 if (err == 0) {
268 Log::err("Reader", Formatter("libphysfs: %: %")
269 % path % PHYSFS_getLastError());
270 return false;
273 return true;
276 bool Reader::resourceExists(const std::string& name)
278 return PHYSFS_exists(name.c_str());
281 bool Reader::directoryExists(const std::string& name)
283 return resourceExists(name) && PHYSFS_isDirectory(name.c_str());
286 bool Reader::fileExists(const std::string& name)
288 return resourceExists(name) && !PHYSFS_isDirectory(name.c_str());
291 Gosu::Buffer* Reader::readBuffer(const std::string& name)
293 Gosu::Buffer* buf = new Gosu::Buffer();
295 if (readFromDisk(name, *buf)) {
296 return buf;
298 else {
299 delete buf;
300 return NULL;
304 std::string Reader::readString(const std::string& name)
306 std::string str;
307 return readFromDisk(name, str) ? str : "";
310 ImageRef Reader::getImage(const std::string& name)
312 ImageRef existing = images.lifetimeRequest(name);
313 if (existing)
314 return existing;
316 boost::scoped_ptr<Gosu::Buffer> buffer(readBuffer(name));
317 if (!buffer)
318 return ImageRef();
320 ImageRef result(Image::create(buffer->data(), buffer->size()));
321 if (!result)
322 return ImageRef();
324 images.lifetimePut(name, result);
325 return result;
328 TiledImageRef Reader::getTiledImage(const std::string& name,
329 int w, int h)
331 TiledImageRef existing = tiles.momentaryRequest(name);
332 if (existing)
333 return existing;
335 boost::scoped_ptr<Gosu::Buffer> buffer(readBuffer(name));
336 if (!buffer)
337 return TiledImageRef();
339 TiledImageRef result(
340 TiledImage::create(buffer->data(), buffer->size(), w, h)
342 if (!result)
343 return TiledImageRef();
345 tiles.momentaryPut(name, result);
346 return result;
349 SampleRef Reader::getSample(const std::string& name)
351 if (!conf.audioEnabled)
352 return SampleRef();
354 SampleRef existing = sounds.lifetimeRequest(name);
355 if (existing)
356 return existing;
358 boost::scoped_ptr<Gosu::Buffer> buffer(readBuffer(name));
359 if (!buffer)
360 return SampleRef();
361 SampleRef result(new Sound(new Gosu::Sample(buffer->frontReader())));
363 sounds.lifetimePut(name, result);
364 return result;
367 XMLRef Reader::getXMLDoc(const std::string& name,
368 const std::string& dtdFile)
370 XMLRef existing = xmls.momentaryRequest(name);
371 if (existing)
372 return existing;
374 XMLRef result(readXMLDoc(name, dtdFile));
376 xmls.momentaryPut(name, result);
377 return result;
380 std::string Reader::getText(const std::string& name)
382 StringRef existing = texts.momentaryRequest(name);
383 if (existing)
384 return *existing.get();
386 StringRef result(new std::string(readString(name)));
388 texts.momentaryPut(name, result);
389 return *result.get();
392 void Reader::garbageCollect()
394 images.garbageCollect();
395 tiles.garbageCollect();
396 sounds.garbageCollect();
397 // songs.garbageCollect();
398 xmls.garbageCollect();
399 texts.garbageCollect();
402 void exportReader()
404 // FIXME: Broken with shift to singleton. No instantiated object to bind.
405 // Fix will require a stub object.
407 #if 0
408 using namespace boost::python;
410 class_<Reader>("Reader", no_init)
411 .def("resource_exists", &Reader::resourceExists)
412 .def("run_python_script", &Reader::runPythonScript)
413 .def("get_text", &Reader::getText);
414 pythonSetGlobal("Reader");
415 #endif