Mercurial > love-experiments
annotate src/modules/filesystem/physfs/Filesystem.cpp @ 1381:4031a4e6cf25
Cleaned up love.filesystem.read/write code
| author | Alex Szpakowski <slime73@gmail.com> |
|---|---|
| date | Sun, 17 Mar 2013 03:30:36 -0300 |
| parents | cb7e512d785c |
| children | 07f9f786e56b |
| rev | line source |
|---|---|
| 1096 | 1 /** |
|
1282
b7f097053d74
Updated copyright text for the new year
Alex Szpakowski <slime73@gmail.com>
parents:
1146
diff
changeset
|
2 * Copyright (c) 2006-2013 LOVE Development Team |
| 1096 | 3 * |
| 4 * This software is provided 'as-is', without any express or implied | |
| 5 * warranty. In no event will the authors be held liable for any damages | |
| 6 * arising from the use of this software. | |
| 7 * | |
| 8 * Permission is granted to anyone to use this software for any purpose, | |
| 9 * including commercial applications, and to alter it and redistribute it | |
| 10 * freely, subject to the following restrictions: | |
| 11 * | |
| 12 * 1. The origin of this software must not be misrepresented; you must not | |
| 13 * claim that you wrote the original software. If you use this software | |
| 14 * in a product, an acknowledgment in the product documentation would be | |
| 15 * appreciated but is not required. | |
| 16 * 2. Altered source versions must be plainly marked as such, and must not be | |
| 17 * misrepresented as being the original software. | |
| 18 * 3. This notice may not be removed or altered from any source distribution. | |
| 19 **/ | |
| 20 | |
| 21 #include "common/config.h" | |
| 22 | |
| 23 #include <iostream> | |
| 24 | |
| 25 #include "common/utf8.h" | |
| 26 #include "common/b64.h" | |
| 27 | |
| 28 #include "Filesystem.h" | |
| 29 | |
| 30 namespace love | |
| 31 { | |
| 32 namespace filesystem | |
| 33 { | |
| 34 namespace physfs | |
| 35 { | |
| 36 | |
| 37 Filesystem::Filesystem() | |
| 38 : open_count(0) | |
| 39 , buffer(0) | |
| 40 , isInited(false) | |
| 41 , release(false) | |
| 42 , releaseSet(false) | |
| 43 { | |
| 44 } | |
| 45 | |
| 46 Filesystem::~Filesystem() | |
| 47 { | |
| 48 if (isInited) | |
| 49 { | |
| 50 isInited = false; | |
| 51 PHYSFS_deinit(); | |
| 52 } | |
| 53 } | |
| 54 | |
| 55 const char *Filesystem::getName() const | |
| 56 { | |
| 57 return "love.filesystem.physfs"; | |
| 58 } | |
| 59 | |
| 60 void Filesystem::init(const char *arg0) | |
| 61 { | |
| 62 if (!PHYSFS_init(arg0)) | |
| 63 throw Exception(PHYSFS_getLastError()); | |
| 64 isInited = true; | |
| 65 } | |
| 66 | |
| 67 void Filesystem::setRelease(bool release) | |
| 68 { | |
| 69 if (releaseSet) | |
| 70 return; | |
| 71 this->release = release; | |
| 72 releaseSet = true; | |
| 73 } | |
| 74 | |
| 75 bool Filesystem::isRelease() const | |
| 76 { | |
| 77 if (!releaseSet) | |
| 78 return false; | |
| 79 return release; | |
| 80 } | |
| 81 | |
| 82 bool Filesystem::setIdentity(const char *ident) | |
| 83 { | |
| 84 if (!isInited) | |
| 85 return false; | |
| 86 | |
| 87 // Store the save directory. | |
| 88 save_identity = std::string(ident); | |
| 89 | |
| 90 // Generate the relative path to the game save folder. | |
| 91 save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity; | |
| 92 | |
| 93 // Generate the full path to the game save folder. | |
| 94 save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR); | |
| 95 if (release) | |
| 96 save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity; | |
| 97 else | |
| 98 save_path_full += save_path_relative; | |
| 99 | |
| 100 // We now have something like: | |
| 101 // save_identity: game | |
| 102 // save_path_relative: ./LOVE/game | |
| 103 // save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game | |
| 104 | |
| 105 // Try to add the save directory to the search path. | |
| 106 // (No error on fail, it means that the path doesn't exist). | |
| 107 PHYSFS_addToSearchPath(save_path_full.c_str(), 0); | |
| 108 | |
| 109 return true; | |
| 110 } | |
| 111 | |
| 112 const char *Filesystem::getIdentity() const | |
| 113 { | |
| 114 return save_identity.c_str(); | |
| 115 } | |
| 116 | |
| 117 bool Filesystem::setSource(const char *source) | |
| 118 { | |
| 119 if (!isInited) | |
| 120 return false; | |
| 121 | |
| 122 // Check whether directory is already set. | |
| 123 if (!game_source.empty()) | |
| 124 return false; | |
| 125 | |
| 126 // Add the directory. | |
| 127 if (!PHYSFS_addToSearchPath(source, 1)) | |
| 128 return false; | |
| 129 | |
| 130 // Save the game source. | |
| 131 game_source = std::string(source); | |
| 132 | |
| 133 return true; | |
| 134 } | |
| 135 | |
| 136 bool Filesystem::setupWriteDirectory() | |
| 137 { | |
| 138 if (!isInited) | |
| 139 return false; | |
| 140 | |
| 141 // These must all be set. | |
| 142 if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty()) | |
| 143 return false; | |
| 144 | |
| 145 // Set the appdata folder as writable directory. | |
| 146 // (We must create the save folder before mounting it). | |
| 147 if (!PHYSFS_setWriteDir(getAppdataDirectory())) | |
| 148 return false; | |
| 149 | |
| 150 // Create the save folder. (We're now "at" %APPDATA%). | |
| 151 bool success = false; | |
| 152 if (release) | |
| 153 success = mkdir(save_identity.c_str()); | |
| 154 else | |
| 155 success = mkdir(save_path_relative.c_str()); | |
| 156 | |
| 157 if (!success) | |
| 158 { | |
| 159 PHYSFS_setWriteDir(0); // Clear the write directory in case of error. | |
| 160 return false; | |
| 161 } | |
| 162 | |
| 163 // Set the final write directory. | |
| 164 if (!PHYSFS_setWriteDir(save_path_full.c_str())) | |
| 165 return false; | |
| 166 | |
| 167 // Add the directory. (Will not be readded if already present). | |
| 168 if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0)) | |
| 169 { | |
| 170 PHYSFS_setWriteDir(0); // Clear the write directory in case of error. | |
| 171 return false; | |
| 172 } | |
| 173 | |
| 174 return true; | |
| 175 } | |
| 176 | |
| 177 File *Filesystem::newFile(const char *filename) | |
| 178 { | |
| 179 return new File(filename); | |
| 180 } | |
| 181 | |
| 182 FileData *Filesystem::newFileData(void *data, unsigned int size, const char *filename) | |
| 183 { | |
| 184 FileData *fd = new FileData(size, std::string(filename)); | |
| 185 | |
| 186 // Copy the data into FileData. | |
| 187 memcpy(fd->getData(), data, size); | |
| 188 | |
| 189 return fd; | |
| 190 } | |
| 191 | |
| 192 FileData *Filesystem::newFileData(const char *b64, const char *filename) | |
| 193 { | |
| 194 int size = strlen(b64); | |
| 195 int outsize = 0; | |
| 196 char *dst = b64_decode(b64, size, outsize); | |
| 197 FileData *fd = new FileData(outsize, std::string(filename)); | |
| 198 | |
| 199 // Copy the data into FileData. | |
| 200 memcpy(fd->getData(), dst, outsize); | |
| 201 delete [] dst; | |
| 202 | |
| 203 return fd; | |
| 204 } | |
| 205 | |
| 206 const char *Filesystem::getWorkingDirectory() | |
| 207 { | |
| 208 if (cwd.empty()) | |
| 209 { | |
| 210 #ifdef LOVE_WINDOWS | |
| 211 | |
| 212 WCHAR w_cwd[LOVE_MAX_PATH]; | |
| 213 _wgetcwd(w_cwd, LOVE_MAX_PATH); | |
| 214 cwd = to_utf8(w_cwd); | |
| 215 replace_char(cwd, '\\', '/'); | |
| 216 #else | |
| 217 char *cwd_char = new char[LOVE_MAX_PATH]; | |
| 218 | |
| 219 if (getcwd(cwd_char, LOVE_MAX_PATH)) | |
| 220 cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty | |
| 221 | |
| 222 delete [] cwd_char; | |
| 223 #endif | |
| 224 } | |
| 225 | |
| 226 return cwd.c_str(); | |
| 227 } | |
| 228 | |
| 229 const char *Filesystem::getUserDirectory() | |
| 230 { | |
| 231 return PHYSFS_getUserDir(); | |
| 232 } | |
| 233 | |
| 234 const char *Filesystem::getAppdataDirectory() | |
| 235 { | |
| 236 #ifdef LOVE_WINDOWS | |
| 237 if (appdata.empty()) | |
| 238 { | |
| 1146 | 239 wchar_t *w_appdata = _wgetenv(L"APPDATA"); |
| 1096 | 240 appdata = to_utf8(w_appdata); |
| 241 replace_char(appdata, '\\', '/'); | |
| 242 } | |
| 243 return appdata.c_str(); | |
| 244 #elif defined(LOVE_MACOSX) | |
| 245 if (appdata.empty()) | |
| 246 { | |
| 247 std::string udir = getUserDirectory(); | |
| 248 udir.append("/Library/Application Support"); | |
| 249 appdata = udir; | |
| 250 } | |
| 251 return appdata.c_str(); | |
| 252 #elif defined(LOVE_LINUX) | |
| 253 if (appdata.empty()) | |
| 254 { | |
| 255 char *xdgdatahome = getenv("XDG_DATA_HOME"); | |
| 256 if (!xdgdatahome) | |
| 257 appdata = std::string(getUserDirectory()) + "/.local/share/"; | |
| 258 else | |
| 259 appdata = xdgdatahome; | |
| 260 } | |
| 261 return appdata.c_str(); | |
| 262 #else | |
| 263 return getUserDirectory(); | |
| 264 #endif | |
| 265 } | |
| 266 | |
| 267 | |
| 268 const char *Filesystem::getSaveDirectory() | |
| 269 { | |
| 270 return save_path_full.c_str(); | |
| 271 } | |
| 272 | |
| 273 bool Filesystem::exists(const char *file) | |
| 274 { | |
| 275 if (PHYSFS_exists(file)) | |
| 276 return true; | |
| 277 return false; | |
| 278 } | |
| 279 | |
| 280 bool Filesystem::isDirectory(const char *file) | |
| 281 { | |
| 282 if (PHYSFS_isDirectory(file)) | |
| 283 return true; | |
| 284 return false; | |
| 285 } | |
| 286 | |
| 287 bool Filesystem::isFile(const char *file) | |
| 288 { | |
| 289 return exists(file) && !isDirectory(file); | |
| 290 } | |
| 291 | |
| 292 bool Filesystem::mkdir(const char *file) | |
| 293 { | |
| 294 if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) | |
| 295 return false; | |
| 296 | |
| 297 if (!PHYSFS_mkdir(file)) | |
| 298 return false; | |
| 299 return true; | |
| 300 } | |
| 301 | |
| 302 bool Filesystem::remove(const char *file) | |
| 303 { | |
| 304 if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) | |
| 305 return false; | |
| 306 | |
| 307 if (!PHYSFS_delete(file)) | |
| 308 return false; | |
| 309 return true; | |
| 310 } | |
| 311 | |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
312 Data *Filesystem::read(const char *filename, int64 size) const |
| 1096 | 313 { |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
314 File file(filename); |
| 1096 | 315 |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
316 file.open(File::READ); |
| 1096 | 317 |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
318 // close() is called in the File destructor. |
|
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
319 return file.read(size); |
| 1096 | 320 } |
| 321 | |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
322 void Filesystem::write(const char *filename, const void *data, int64 size) |
| 1096 | 323 { |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
324 File file(filename); |
| 1096 | 325 |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
326 file.open(File::WRITE); |
| 1096 | 327 |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
328 // close() is called in the File destructor. |
|
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
329 if (!file.write(data, size)) |
|
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
330 throw love::Exception("Data could not be written."); |
| 1096 | 331 } |
| 332 | |
| 333 int Filesystem::enumerate(lua_State *L) | |
| 334 { | |
| 335 int n = lua_gettop(L); | |
| 336 | |
| 337 if (n != 1) | |
| 338 return luaL_error(L, "Function requires a single parameter."); | |
| 339 | |
| 340 int type = lua_type(L, 1); | |
| 341 | |
| 342 if (type != LUA_TSTRING) | |
| 343 return luaL_error(L, "Function requires parameter of type string."); | |
| 344 | |
| 345 const char *dir = lua_tostring(L, 1); | |
| 346 char **rc = PHYSFS_enumerateFiles(dir); | |
| 347 char **i; | |
| 348 int index = 1; | |
| 349 | |
| 350 lua_newtable(L); | |
| 351 | |
| 352 for (i = rc; *i != 0; i++) | |
| 353 { | |
| 354 lua_pushinteger(L, index); | |
| 355 lua_pushstring(L, *i); | |
| 356 lua_settable(L, -3); | |
| 357 index++; | |
| 358 } | |
| 359 | |
| 360 PHYSFS_freeList(rc); | |
| 361 | |
| 362 return 1; | |
| 363 } | |
| 364 | |
| 365 int Filesystem::lines_i(lua_State *L) | |
| 366 { | |
| 367 const int bufsize = 1024; | |
| 368 char buf[bufsize]; | |
| 369 int linesize = 0; | |
| 370 bool newline = false; | |
| 371 | |
| 372 File *file = luax_checktype<File>(L, lua_upvalueindex(1), "File", FILESYSTEM_FILE_T); | |
| 373 | |
| 374 // Only accept read mode at this point. | |
| 375 if (file->getMode() != File::READ) | |
| 376 return luaL_error(L, "File needs to stay in read mode."); | |
| 377 | |
| 378 int64 pos = file->tell(); | |
| 379 int64 userpos = -1; | |
| 380 | |
| 381 if (lua_isnoneornil(L, lua_upvalueindex(2)) == 0) | |
| 382 { | |
| 383 // User may have changed the file position. | |
| 384 userpos = pos; | |
| 385 pos = (int64) lua_tonumber(L, lua_upvalueindex(2)); | |
| 386 if (userpos != pos) | |
| 387 file->seek(pos); | |
| 388 } | |
| 389 | |
| 390 while (!newline && !file->eof()) | |
| 391 { | |
| 392 // This 64-bit to 32-bit integer cast should be safe as it never exceeds bufsize. | |
| 393 int read = (int) file->read(buf, bufsize); | |
| 394 if (read < 0) | |
| 395 return luaL_error(L, "Could not read from file."); | |
| 396 | |
| 397 linesize += read; | |
| 398 | |
| 399 for (int i = 0; i < read; i++) | |
| 400 { | |
| 401 if (buf[i] == '\n') | |
| 402 { | |
| 403 linesize -= read - i; | |
| 404 newline = true; | |
| 405 break; | |
| 406 } | |
| 407 } | |
| 408 } | |
| 409 | |
| 410 if (newline || (file->eof() && linesize > 0)) | |
| 411 { | |
| 412 if (linesize < bufsize) | |
| 413 { | |
| 414 // We have the line in the buffer on the stack. No 'new' and 'read' needed. | |
| 415 lua_pushlstring(L, buf, linesize > 0 && buf[linesize - 1] == '\r' ? linesize - 1 : linesize); | |
| 416 if (userpos < 0) | |
| 417 file->seek(pos + linesize + 1); | |
| 418 } | |
| 419 else | |
| 420 { | |
| 421 char *str; | |
| 422 try | |
| 423 { | |
| 424 str = new char[linesize + 1]; | |
| 425 } | |
| 426 catch(std::bad_alloc &) | |
| 427 { | |
| 428 return luaL_error(L, "Out of memory"); | |
| 429 } | |
| 430 file->seek(pos); | |
| 431 | |
| 432 // Read the \n anyway and save us a call to seek. | |
| 433 if (file->read(str, linesize + 1) == -1) | |
| 434 { | |
| 435 delete [] str; | |
| 436 return luaL_error(L, "Could not read from file."); | |
| 437 } | |
| 438 | |
| 439 lua_pushlstring(L, str, str[linesize - 1] == '\r' ? linesize - 1 : linesize); | |
| 440 delete [] str; | |
| 441 } | |
| 442 | |
| 443 if (userpos >= 0) | |
| 444 { | |
| 445 // Save new position in upvalue. | |
| 446 lua_pushnumber(L, (lua_Number)(pos + linesize + 1)); | |
| 447 lua_replace(L, lua_upvalueindex(2)); | |
| 448 file->seek(userpos); | |
| 449 } | |
| 450 | |
| 451 return 1; | |
| 452 } | |
| 453 | |
| 454 // EOF reached. | |
| 455 if (userpos >= 0 && luax_toboolean(L, lua_upvalueindex(3))) | |
| 456 file->seek(userpos); | |
| 457 else | |
| 458 file->close(); | |
| 459 | |
| 460 return 0; | |
| 461 } | |
| 462 | |
| 463 int Filesystem::load(lua_State *L) | |
| 464 { | |
| 465 // Need only one arg. | |
| 466 luax_assert_argc(L, 1, 1); | |
| 467 | |
| 468 // Must be string. | |
| 469 if (!lua_isstring(L, -1)) | |
| 470 return luaL_error(L, "The argument must be a string."); | |
| 471 | |
| 472 std::string filename = lua_tostring(L, -1); | |
| 473 | |
| 474 // The file must exist. | |
| 475 if (!exists(filename.c_str())) | |
| 476 return luaL_error(L, "File %s does not exist.", filename.c_str()); | |
| 477 | |
| 478 // Create the file. | |
| 479 File *file = newFile(filename.c_str()); | |
| 480 | |
| 481 // Get the data from the file. | |
| 482 Data *data = file->read(); | |
| 483 | |
| 484 int status = luaL_loadbuffer(L, (const char *)data->getData(), data->getSize(), ("@" + filename).c_str()); | |
| 485 | |
| 486 data->release(); | |
| 487 file->release(); | |
| 488 | |
| 489 // Load the chunk, but don't run it. | |
| 490 switch (status) | |
| 491 { | |
| 492 case LUA_ERRMEM: | |
| 493 return luaL_error(L, "Memory allocation error: %s\n", lua_tostring(L, -1)); | |
| 494 case LUA_ERRSYNTAX: | |
| 495 return luaL_error(L, "Syntax error: %s\n", lua_tostring(L, -1)); | |
| 496 default: // success | |
| 497 return 1; | |
| 498 } | |
| 499 } | |
| 500 | |
| 501 int Filesystem::getLastModified(lua_State *L) | |
| 502 { | |
| 503 const char *filename = luaL_checkstring(L, 1); | |
| 504 PHYSFS_sint64 time = PHYSFS_getLastModTime(filename); | |
| 505 if (time == -1) | |
| 506 { | |
| 507 lua_pushnil(L); | |
| 508 lua_pushstring(L, "Could not determine file modification date."); | |
| 509 return 2; | |
| 510 } | |
| 511 lua_pushnumber(L, static_cast<lua_Number>(time)); | |
| 512 return 1; | |
| 513 } | |
| 514 | |
|
1380
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
515 int64 Filesystem::getSize(const char *filename) |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
516 { |
|
1381
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
517 File file(filename); |
|
4031a4e6cf25
Cleaned up love.filesystem.read/write code
Alex Szpakowski <slime73@gmail.com>
parents:
1380
diff
changeset
|
518 int64 size = file.getSize(); |
|
1380
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
519 return size; |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
520 } |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
521 |
| 1096 | 522 } // physfs |
| 523 } // filesystem | |
| 524 } // love |
