diff --git a/design.txt b/design.txt index f9169f34..4f3bd2bf 100644 --- a/design.txt +++ b/design.txt @@ -161,24 +161,27 @@ On-disk files: (lib/file.cpp) enum OpenMode { OPEN_READ, OPEN_WRITE, + NOT_OPEN, } - File: class File { private: - union { - ifstream in; - ofstream out; - } f; + ifstream in; + ofstream out; + unsigned int version; OpenMode mode; FileLocHint hint; string filepath; + public: File(string, FileLocHint); + ~File(); const char *get_filepath(); bool exists(); bool open(OpenMode); + bool close(); const File &operator<<(File &); const File &operator>>(File &); @@ -198,6 +201,9 @@ On-disk files: (lib/file.cpp) $HOME/.ocarina/ $HOME/.ocarina-debug/ + File : ~File() + Close the file stream if it is open. + const char *get_filepath() Return the full filepath to the file. @@ -218,7 +224,11 @@ On-disk files: (lib/file.cpp) - Write version information to the start of the file - Return true - Return false if there are any errors opening the file. + Return false if the file is already open. + Return false if there are any other errors. + + void File : close() + Close a file after IO. File : operator<<() Write data to the file. diff --git a/design/file.txt b/design/file.txt index a33b2b7a..7e728621 100644 --- a/design/file.txt +++ b/design/file.txt @@ -42,24 +42,27 @@ On-disk files: (lib/file.cpp) enum OpenMode { OPEN_READ, OPEN_WRITE, + NOT_OPEN, } - File: class File { private: - union { - ifstream in; - ofstream out; - } f; + ifstream in; + ofstream out; + unsigned int version; OpenMode mode; FileLocHint hint; string filepath; + public: File(string, FileLocHint); + ~File(); const char *get_filepath(); bool exists(); bool open(OpenMode); + bool close(); const File &operator<<(File &); const File &operator>>(File &); @@ -79,6 +82,9 @@ On-disk files: (lib/file.cpp) $HOME/.ocarina/ $HOME/.ocarina-debug/ + File : ~File() + Close the file stream if it is open. + const char *get_filepath() Return the full filepath to the file. @@ -99,7 +105,11 @@ On-disk files: (lib/file.cpp) - Write version information to the start of the file - Return true - Return false if there are any errors opening the file. + Return false if the file is already open. + Return false if there are any other errors. + + void File : close() + Close a file after IO. File : operator<<() Write data to the file. diff --git a/include/file.h b/include/file.h index 0e5d3760..db1127b4 100644 --- a/include/file.h +++ b/include/file.h @@ -4,22 +4,42 @@ #ifndef OCARINA_FILE_H #define OCARINA_FILE_H +#include #include +#define FILE_VERSION 0 + enum FileLocHint { FILE_TYPE_CONFIG, FILE_TYPE_DATA, FILE_TYPE_LEGACY, }; +enum OpenMode { + OPEN_READ, + OPEN_WRITE, + NOT_OPEN, +}; + class File { private: + std::fstream stream; + OpenMode mode; FileLocHint hint; std::string filepath; + unsigned int version; + + void find_dir(std::string &); + bool open_read(); + bool open_write(); public: File(std::string, FileLocHint); + ~File(); const char *get_filepath(); + bool exists(); + bool open(OpenMode); + bool close(); }; #endif /* OCARINA_FILE_H */ diff --git a/include/test.h b/include/test.h new file mode 100644 index 00000000..2d1d33fd --- /dev/null +++ b/include/test.h @@ -0,0 +1,9 @@ +#ifndef OCARINA_TEST_H +#define OCARINA_TEST_H +#ifdef CONFIG_TEST + +void rm_test_config(); +void rm_test_data(); + +#endif /* CONFIG_TEST */ +#endif /* OCARINA_TEST_H */ diff --git a/lib/Sconscript b/lib/Sconscript index 73c00c68..5294eb82 100644 --- a/lib/Sconscript +++ b/lib/Sconscript @@ -7,4 +7,7 @@ if CONFIG.FILE: CONFIG.package("glib-2.0") build += [ env.Object("file.cpp") ] +if CONFIG.TEST: + build += [ env.Object("test.cpp") ] + Return("build") diff --git a/lib/file.cpp b/lib/file.cpp index 478602c5..e7c789cd 100644 --- a/lib/file.cpp +++ b/lib/file.cpp @@ -2,6 +2,7 @@ * Copyright 2013 (c) Bryan Schumaker. */ #include +#include #include @@ -13,37 +14,118 @@ #define OCARINA_DIR "ocarina" #endif +void File :: find_dir(std::string &res) +{ + switch (hint) { + case FILE_TYPE_CONFIG: + res = g_get_user_config_dir(); + break; + case FILE_TYPE_DATA: + res = g_get_user_data_dir(); + break; + case FILE_TYPE_LEGACY: + res = g_get_home_dir(); + } + + res += "/"; + switch (hint) { + case FILE_TYPE_CONFIG: + case FILE_TYPE_DATA: + res += OCARINA_DIR; + break; + case FILE_TYPE_LEGACY: + res += "."; + res += OCARINA_DIR; + } +} + File :: File(std::string path, FileLocHint file_hint) - : hint(file_hint) + : mode(NOT_OPEN), hint(file_hint), version(FILE_VERSION) { std::string dir; - switch (file_hint) { - case FILE_TYPE_CONFIG: - dir = g_get_user_config_dir(); - break; - case FILE_TYPE_DATA: - dir = g_get_user_data_dir(); - break; - case FILE_TYPE_LEGACY: - dir = g_get_home_dir(); - } - - dir += "/"; - switch (file_hint) { - case FILE_TYPE_CONFIG: - case FILE_TYPE_DATA: - dir += OCARINA_DIR; - break; - case FILE_TYPE_LEGACY: - dir += "."; - dir += OCARINA_DIR; - } - + find_dir(dir); filepath = dir + "/" + path; } +File :: ~File() +{ + close(); +} + const char *File :: get_filepath() { return filepath.c_str(); } + +bool File :: exists() +{ + return g_file_test(filepath.c_str(), G_FILE_TEST_EXISTS); +} + +bool File :: open_read() +{ + if (!exists()) { + dprint("ERROR: File does not exist (%s)\n", filepath.c_str()); + return false; + } + + stream.open(filepath.c_str(), std::fstream::in); + if (stream.fail()) { + dprint("ERROR: Could not open file for reading (%s)\n", filepath.c_str()); + return false; + } + + mode = OPEN_READ; + stream >> version; + return stream.good(); +} + +bool File :: open_write() +{ + std::string dir; + if (hint == FILE_TYPE_LEGACY) { + dprint("ERROR: Cannot write to legacy files (%s)\n", filepath.c_str()); + return false; + } + + find_dir(dir); + if (g_mkdir_with_parents(dir.c_str(), 0755) != 0) { + dprint("ERROR: Could not make directory (%s)\n", dir.c_str()); + return false; + } + + stream.open(filepath.c_str(), std::fstream::out); + if (stream.fail()) { + dprint("ERROR: Could not open file for writing (%s)\n", filepath.c_str()); + return false; + } + + mode = OPEN_WRITE; + stream << version << std::endl; + return stream.good(); +} + +bool File :: open(OpenMode m) +{ + if (mode != NOT_OPEN) { + dprint("ERROR: File is already open (%s)\n", filepath.c_str()); + return false; + } + + if (m == NOT_OPEN) { + dprint("ERROR: NOT_OPEN is not a legal OpenMode (%s)\n", filepath.c_str()); + return false; + } else if (m == OPEN_READ) + return open_read(); + else /* m == OPEN_WRITE */ + return open_write(); +} + +bool File :: close() +{ + if (mode != NOT_OPEN) + stream.close(); + mode = NOT_OPEN; + return stream.good(); +} diff --git a/lib/test.cpp b/lib/test.cpp new file mode 100644 index 00000000..2d45ba5d --- /dev/null +++ b/lib/test.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2013 (c) Bryan Schumaker. + */ +#include + +#include +#include +#include + +/* A bit hackish, but it works... */ +static void rm_dir(const char *dir) +{ + std::string cmd = "rm -frv "; + cmd += dir; + cmd += "/ocarina-test"; + system(cmd.c_str()); +} + +void rm_test_config() +{ + rm_dir(g_get_user_config_dir()); +} + +void rm_test_data() +{ + rm_dir(g_get_user_data_dir()); +} diff --git a/tests/file/Sconscript b/tests/file/Sconscript index a48edec3..8857db5d 100644 --- a/tests/file/Sconscript +++ b/tests/file/Sconscript @@ -4,3 +4,4 @@ Import("Test", "CONFIG") CONFIG.FILE = True Test("file", "compile.cpp") +Test("file", "open.cpp") diff --git a/tests/file/open-debug.good b/tests/file/open-debug.good new file mode 100644 index 00000000..a685f26c --- /dev/null +++ b/tests/file/open-debug.good @@ -0,0 +1,22 @@ +removed ‘/home/anna/.local/share/ocarina-test/test.4’ +removed ‘/home/anna/.local/share/ocarina-test/test.3’ +removed directory: ‘/home/anna/.local/share/ocarina-test’ + +ERROR: NOT_OPEN is not a legal OpenMode (/home/anna/.local/share/ocarina-test/test.0) +Test 0: Passed + +ERROR: File does not exist (/home/anna/.local/share/ocarina-test/test.1) +Test 1: Passed + +ERROR: Cannot write to legacy files (/home/anna/.ocarina-test/test.2) +Test 2: Passed + +Test 3a: Passed +ERROR: File is already open (/home/anna/.local/share/ocarina-test/test.3) +Test 3b: Passed + +Test 4a: Passed +Test 4b: Passed +Test 4c: Passed + +Test 5: Passed diff --git a/tests/file/open.cpp b/tests/file/open.cpp new file mode 100644 index 00000000..60a88e15 --- /dev/null +++ b/tests/file/open.cpp @@ -0,0 +1,106 @@ +/* + * Test opening files under various conditions + */ +#include +#include +#include + +static int test_result(const char *num, bool res, bool expected) +{ + print("Test %s: ", num); + if (res == expected) { + print("Passed\n"); + return 0; + } else { + print("Failed\n"); + return 1; + } +} + +static int test_error(const char *num, FileLocHint hint, OpenMode mode) +{ + std::string f = "test."; + f += num; + + File file(f.c_str(), hint); + print("\n"); + return test_result(num, file.open(mode), false); +} + +/* + * Attempt to open a file with mode == NOT_OPEN + */ +int test_0() +{ + return test_error("0", FILE_TYPE_DATA, NOT_OPEN); +} + +/* + * Attempt to open a file that doesn't exist for reading + */ +int test_1() +{ + return test_error("1", FILE_TYPE_DATA, OPEN_READ); +} + +/* + * Attempt to open a legacy file for writing + */ +int test_2() +{ + return test_error("2", FILE_TYPE_LEGACY, OPEN_WRITE); +} + +/* + * Write to a file, then open it for reading WITHOUT closing first + */ +int test_3() +{ + File file("test.3", FILE_TYPE_DATA); + + printf("\n"); + if (test_result("3a", file.open(OPEN_WRITE), true) != 0) + return 1; + return test_result("3b", file.open(OPEN_READ), false); +} + +/* + * Write to a file, close it, then open it again for reading + */ +int test_4() +{ + File file("test.4", FILE_TYPE_DATA); + + printf("\n"); + if (test_result("4a", file.open(OPEN_WRITE), true) != 0) + return 1; + if (test_result("4b", file.close(), true) != 0) + return 1; + return test_result("4c", file.open(OPEN_READ), true); +} + +/* + * Attempt to close a file that was never opened + */ +int test_5() +{ + File file("test.5", FILE_TYPE_DATA); + + print("\n"); + return test_result("5", file.close(), true); +} + +int main(int argc, char **argv) +{ + int failed = 0; + rm_test_data(); + + failed += test_0(); + failed += test_1(); + failed += test_2(); + failed += test_3(); + failed += test_4(); + failed += test_5(); + + return failed; +} diff --git a/tests/file/open.good b/tests/file/open.good new file mode 100644 index 00000000..0b588752 --- /dev/null +++ b/tests/file/open.good @@ -0,0 +1,18 @@ +removed ‘/home/anna/.local/share/ocarina-test/test.4’ +removed ‘/home/anna/.local/share/ocarina-test/test.3’ +removed directory: ‘/home/anna/.local/share/ocarina-test’ + +Test 0: Passed + +Test 1: Passed + +Test 2: Passed + +Test 3a: Passed +Test 3b: Passed + +Test 4a: Passed +Test 4b: Passed +Test 4c: Passed + +Test 5: Passed