diff --git a/libopenage/util/filelike/filelike.h b/libopenage/util/filelike/filelike.h index e99382c6a6..b9715465af 100644 --- a/libopenage/util/filelike/filelike.h +++ b/libopenage/util/filelike/filelike.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once @@ -56,11 +56,14 @@ class OAAPI FileLike { * * Sync the numbers with the fslike/cpp.pyx * because Cython can't enum class yet. + * TODO: once cython can handle enum class, add pxd annotation. */ enum class mode_t : int { R = 0, W = 1, - RW = 2 + RW = 2, + A = 3, + AR = 4, }; FileLike(); diff --git a/libopenage/util/filelike/native.cpp b/libopenage/util/filelike/native.cpp index 499f8b2db2..1c36d3427d 100644 --- a/libopenage/util/filelike/native.cpp +++ b/libopenage/util/filelike/native.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "native.h" @@ -28,6 +28,12 @@ Native::Native(const std::string &path, mode_t mode) case mode_t::RW: open_mode = std::ios_base::in | std::ios_base::out; break; + case mode_t::A: + open_mode = std::ios_base::out | std::ios_base::ate; + break; + case mode_t::AR: + open_mode = std::ios_base::in | std::ios_base::out | std::ios_base::ate; + break; default: throw Error{ERR << "unknown open mode"}; } diff --git a/libopenage/util/fslike/directory.cpp b/libopenage/util/fslike/directory.cpp index 9acffa3868..5c47069db7 100644 --- a/libopenage/util/fslike/directory.cpp +++ b/libopenage/util/fslike/directory.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "directory.h" @@ -52,6 +52,10 @@ Directory::Directory(const std::string &basepath, bool create_if_missing) } +// We don't need a resolve_r and resolve_w here! +// If the underlying fslike system is a Directory (i.e. this.) +// then we don't have any overlay possibility! +// -> Always resolve just the real system filename. std::string Directory::resolve(const Path::parts_t &parts) const { std::string ret = this->basepath; for (auto &part : parts) { @@ -192,6 +196,30 @@ File Directory::open_w(const Path::parts_t &parts) { } +File Directory::open_rw(const Path::parts_t &parts) { + return File{ + std::make_shared(this->resolve(parts), + filelike::Native::mode_t::RW) + }; +} + + +File Directory::open_a(const Path::parts_t &parts) { + return File{ + std::make_shared(this->resolve(parts), + filelike::Native::mode_t::A) + }; +} + + +File Directory::open_ar(const Path::parts_t &parts) { + return File{ + std::make_shared(this->resolve(parts), + filelike::Native::mode_t::AR) + }; +} + + std::string Directory::get_native_path(const Path::parts_t &parts) { return this->resolve(parts); } diff --git a/libopenage/util/fslike/directory.h b/libopenage/util/fslike/directory.h index 1888ca9e68..314d7d58f8 100644 --- a/libopenage/util/fslike/directory.h +++ b/libopenage/util/fslike/directory.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once @@ -32,6 +32,10 @@ class Directory : public FSLike { bool mkdirs(const Path::parts_t &parts) override; File open_r(const Path::parts_t &parts) override; File open_w(const Path::parts_t &parts) override; + File open_rw(const Path::parts_t &parts) override; + File open_a(const Path::parts_t &parts) override; + File open_ar(const Path::parts_t &parts) override; + // inherit the resolve_r/resolve_w functions std::string get_native_path(const Path::parts_t &parts) override; bool rename(const Path::parts_t &parts, const Path::parts_t &target_parts) override; diff --git a/libopenage/util/fslike/fslike.h b/libopenage/util/fslike/fslike.h index d254708712..5973782f8c 100644 --- a/libopenage/util/fslike/fslike.h +++ b/libopenage/util/fslike/fslike.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once @@ -47,6 +47,9 @@ namespace fslike { * * File open_r(const parts_t &parts) except + * File open_w(const parts_t &parts) except + + * File open_rw(const parts_t &parts) except + + * File open_a(const parts_t &parts) except + + * File open_ar(const parts_t &parts) except + * pair[bool, Path] resolve_r(const parts_t &parts) except + * pair[bool, Path] resolve_w(const parts_t &parts) except + * string get_native_path(const parts_t &parts) except + @@ -78,6 +81,12 @@ class OAAPI FSLike : public std::enable_shared_from_this { virtual bool mkdirs(const Path::parts_t &parts) = 0; virtual File open_r(const Path::parts_t &parts) = 0; virtual File open_w(const Path::parts_t &parts) = 0; + virtual File open_rw(const Path::parts_t &parts) = 0; + virtual File open_a(const Path::parts_t &parts) = 0; + virtual File open_ar(const Path::parts_t &parts) = 0; + + // provide a default implementation that resolves the path + // by checking if it is readable/writable: virtual std::pair resolve_r(const Path::parts_t &parts); virtual std::pair resolve_w(const Path::parts_t &parts); virtual std::string get_native_path(const Path::parts_t &parts) = 0; diff --git a/libopenage/util/fslike/python.cpp b/libopenage/util/fslike/python.cpp index 13ec4cb234..5e514e7814 100644 --- a/libopenage/util/fslike/python.cpp +++ b/libopenage/util/fslike/python.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "python.h" @@ -42,6 +42,18 @@ File Python::open_w(const Path::parts_t &parts) { return pyx_fs_open_w.call(this->fsobj->get_ref(), parts); } +File Python::open_rw(const Path::parts_t &parts) { + return pyx_fs_open_rw.call(this->fsobj->get_ref(), parts); +} + +File Python::open_a(const Path::parts_t &parts) { + return pyx_fs_open_a.call(this->fsobj->get_ref(), parts); +} + +File Python::open_ar(const Path::parts_t &parts) { + return pyx_fs_open_ar.call(this->fsobj->get_ref(), parts); +} + std::pair Python::resolve_r(const Path::parts_t &parts) { auto path = pyx_fs_resolve_r.call(this->fsobj->get_ref(), parts); return std::make_pair(path.get_fsobj() != nullptr, path); @@ -113,6 +125,9 @@ pyinterface::PyIfFunc, PyObject *, const std::vector&> pyx_fs_mkdirs; pyinterface::PyIfFunc&> pyx_fs_open_r; pyinterface::PyIfFunc&> pyx_fs_open_w; +pyinterface::PyIfFunc&> pyx_fs_open_rw; +pyinterface::PyIfFunc&> pyx_fs_open_a; +pyinterface::PyIfFunc&> pyx_fs_open_ar; pyinterface::PyIfFunc&> pyx_fs_resolve_r; pyinterface::PyIfFunc&> pyx_fs_resolve_w; diff --git a/libopenage/util/fslike/python.h b/libopenage/util/fslike/python.h index 9a12cd5749..1766b974f2 100644 --- a/libopenage/util/fslike/python.h +++ b/libopenage/util/fslike/python.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once @@ -49,6 +49,10 @@ class Python : public FSLike { bool mkdirs(const Path::parts_t &parts) override; File open_r(const Path::parts_t &parts) override; File open_w(const Path::parts_t &parts) override; + File open_rw(const Path::parts_t &parts) override; + File open_a(const Path::parts_t &parts) override; + File open_ar(const Path::parts_t &parts) override; + // specialize the resolve functions to relay them to python. std::pair resolve_r(const Path::parts_t &parts) override; std::pair resolve_w(const Path::parts_t &parts) override; std::string get_native_path(const Path::parts_t &parts) override; @@ -92,6 +96,15 @@ extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_w; +// pxd: PyIfFunc2[File, PyObjectPtr, const vector[string]] pyx_fs_open_rw +extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_rw; + +// pxd: PyIfFunc2[File, PyObjectPtr, const vector[string]] pyx_fs_open_a +extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_a; + +// pxd: PyIfFunc2[File, PyObjectPtr, const vector[string]] pyx_fs_open_ar +extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_ar; + // pxd: PyIfFunc2[Path, PyObjectPtr, const vector[string]] pyx_fs_resolve_r extern OAAPI pyinterface::PyIfFunc&> pyx_fs_resolve_r; diff --git a/libopenage/util/path.cpp b/libopenage/util/path.cpp index 497b4630ba..d15ce06303 100644 --- a/libopenage/util/path.cpp +++ b/libopenage/util/path.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "path.h" @@ -115,6 +115,15 @@ File Path::open(const std::string &mode) const { else if (mode == "w") { return this->open_w(); } + else if (mode == "rw" or mode == "r+") { + return this->open_w(); + } + else if (mode == "a") { + return this->open_a(); + } + else if (mode == "a+" or mode == "ar") { + return this->open_ar(); + } else { throw Error{ERR << "unsupported open mode: " << mode}; } @@ -131,6 +140,21 @@ File Path::open_w() const { } +File Path::open_rw() const { + return this->fsobj->open_rw(this->parts); +} + + +File Path::open_a() const { + return this->fsobj->open_a(this->parts); +} + + +File Path::open_ar() const { + return this->fsobj->open_ar(this->parts); +} + + std::string Path::get_native_path() const { return this->fsobj->get_native_path(this->parts); } diff --git a/libopenage/util/path.h b/libopenage/util/path.h index 64b71aaaff..a67d4224ab 100644 --- a/libopenage/util/path.h +++ b/libopenage/util/path.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once @@ -94,6 +94,9 @@ class OAAPI Path { File open(const std::string &mode="r") const; File open_r() const; File open_w() const; + File open_rw() const; + File open_a() const; + File open_ar() const; /** * Resolve the native path by flattening all underlying diff --git a/openage/util/fslike/abstract.py b/openage/util/fslike/abstract.py index 80716c5a65..2f94139d95 100644 --- a/openage/util/fslike/abstract.py +++ b/openage/util/fslike/abstract.py @@ -1,4 +1,4 @@ -# Copyright 2015-2017 the openage authors. See copying.md for legal info. +# Copyright 2015-2018 the openage authors. See copying.md for legal info. """ Provides filesystem-like interfaces: @@ -64,6 +64,18 @@ def open_w(self, parts): """ Shall return a BufferedWriter for the given file ("mode 'wb'"). """ pass + def open_rw(self, parts): + """ Shall return a BufferedWriter for the given file ("mode 'r+'"). """ + pass + + def open_a(self, parts): + """ Shall return a BufferedWriter for the given file ("mode 'a'"). """ + pass + + def open_ar(self, parts): + """ Shall return a BufferedWriter for the given file ("mode 'a+'"). """ + pass + def exists(self, parts): """ Test if the parts are a file or a directory """ return self.is_file(parts) or self.is_dir(parts) diff --git a/openage/util/fslike/cpp.pyx b/openage/util/fslike/cpp.pyx index 980cad542a..8470fe4ce9 100644 --- a/openage/util/fslike/cpp.pyx +++ b/openage/util/fslike/cpp.pyx @@ -1,4 +1,4 @@ -# Copyright 2017-2017 the openage authors. See copying.md for legal info. +# Copyright 2017-2018 the openage authors. See copying.md for legal info. """ Functions called from C++ to perform method calls on @@ -27,6 +27,9 @@ from libopenage.util.fslike.python cimport ( pyx_fs_mkdirs, pyx_fs_open_r, pyx_fs_open_w, + pyx_fs_open_rw, + pyx_fs_open_a, + pyx_fs_open_ar, pyx_fs_resolve_r, pyx_fs_resolve_w, pyx_fs_get_native_path, @@ -253,7 +256,20 @@ cdef File_cpp fs_open(object path, int mode) except *: return File_cpp(native_path, mode) else: - access_mode = "rb" if mode == 0 else "wb" + # sync with filelike/filelike.h enum class mode_t + # (and the calls to fs_open_* below) + if mode == 0: + access_mode = 'rb' + elif mode == 1: + access_mode = 'wb' + elif mode == 2: + access_mode = 'r+b' + elif mode == 3: + access_mode = 'ab' + elif mode == 4: + access_mode = 'a+b' + else: + raise ValueError("unknown file open mode id: %s" % mode) # open it the python-way and wrap it filelike = path.open(access_mode) @@ -271,6 +287,21 @@ cdef File_cpp fs_open_w(PyObject *fslike, const vector[string]& parts) except * return fs_open(open_path, 1) +cdef File_cpp fs_open_rw(PyObject *fslike, const vector[string]& parts) except * with gil: + open_path = ( fslike).resolve_w(parts) + return fs_open(open_path, 2) + + +cdef File_cpp fs_open_a(PyObject *fslike, const vector[string]& parts) except * with gil: + open_path = ( fslike).resolve_w(parts) + return fs_open(open_path, 3) + + +cdef File_cpp fs_open_ar(PyObject *fslike, const vector[string]& parts) except * with gil: + open_path = ( fslike).resolve_w(parts) + return fs_open(open_path, 4) + + cdef Path_cpp fs_resolve_r(PyObject *fslike, const vector[string]& parts) except * with gil: path = ( fslike).resolve_r(parts) if path is not None: @@ -333,6 +364,9 @@ def setup(): pyx_fs_mkdirs.bind0(fs_mkdirs) pyx_fs_open_r.bind0(fs_open_r) pyx_fs_open_w.bind0(fs_open_w) + pyx_fs_open_rw.bind0(fs_open_rw) + pyx_fs_open_a.bind0(fs_open_a) + pyx_fs_open_ar.bind0(fs_open_ar) pyx_fs_resolve_r.bind0(fs_resolve_r) pyx_fs_resolve_w.bind0(fs_resolve_w) pyx_fs_get_native_path.bind0(fs_get_native_path) diff --git a/openage/util/fslike/directory.py b/openage/util/fslike/directory.py index 4338146132..ab6b09a5b1 100644 --- a/openage/util/fslike/directory.py +++ b/openage/util/fslike/directory.py @@ -1,4 +1,4 @@ -# Copyright 2015-2017 the openage authors. See copying.md for legal info. +# Copyright 2015-2018 the openage authors. See copying.md for legal info. """ FSLikeObjects that represent actual file system paths: @@ -51,6 +51,15 @@ def open_r(self, parts): def open_w(self, parts): return open(self.resolve(parts), 'wb') + def open_rw(self, parts): + return open(self.resolve(parts), 'r+b') + + def open_a(self, parts): + return open(self.resolve(parts), 'ab') + + def open_ar(self, parts): + return open(self.resolve(parts), 'a+b') + def get_native_path(self, parts): return self.resolve(parts) diff --git a/openage/util/fslike/path.py b/openage/util/fslike/path.py index 170c381171..2066efb298 100644 --- a/openage/util/fslike/path.py +++ b/openage/util/fslike/path.py @@ -1,4 +1,4 @@ -# Copyright 2015-2017 the openage authors. See copying.md for legal info. +# Copyright 2015-2018 the openage authors. See copying.md for legal info. """ Provides Path, which is analogous to pathlib.Path, @@ -105,17 +105,32 @@ def mkdirs(self): def open(self, mode="r"): """ Opens the file at this path; returns a file-like object. """ - if mode == "rb": - return self.fsobj.open_r(self.parts) - elif mode == "r": - return TextIOWrapper(self.fsobj.open_r(self.parts)) - elif mode == "wb": - return self.fsobj.open_w(self.parts) - elif mode == "w": - return TextIOWrapper(self.fsobj.open_w(self.parts)) + + dmode = mode.replace("b", "") + + if dmode == "r": + handle = self.fsobj.open_r(self.parts) + + elif dmode == "w": + handle = self.fsobj.open_w(self.parts) + + elif dmode == "r+" or dmode == "rw": + handle = self.fsobj.open_rw(self.parts) + + elif dmode == "a": + handle = self.fsobj.open_a(self.parts) + + elif dmode == "a+" or dmode == "ar": + handle = self.fsobj.open_ar(self.parts) + else: raise UnsupportedOperation("unsupported open mode: " + mode) + if "b" in mode: + return handle + + return TextIOWrapper(handle) + def open_r(self): """ open with mode='rb' """ return self.fsobj.open_r(self.parts) diff --git a/openage/util/fslike/test.py b/openage/util/fslike/test.py index 22665826b6..fe0df2f19a 100644 --- a/openage/util/fslike/test.py +++ b/openage/util/fslike/test.py @@ -1,4 +1,4 @@ -# Copyright 2017-2017 the openage authors. See copying.md for legal info. +# Copyright 2017-2018 the openage authors. See copying.md for legal info. """ Tests for the filesystem-like abstraction. """ @@ -154,15 +154,19 @@ def test_case_ignoring(root_path, root_dir): # open it with wrong-case name with ignorecase_dir["LeMmE_In"].open("rb") as fil: + assert_value(fil.readable(), True) + assert_value(fil.writable(), False) assert_value(fil.read(), b"pwnt") # then write it with wrong-case name with ignorecase_dir["LeMmE_In"].open("wb") as fil: - fil.write(b"changedit") + assert_value(fil.readable(), False) + assert_value(fil.writable(), True) + fil.write(b"yay") # check if changes went to known-name file with root_path["lemme_in"].open("rb") as fil: - assert_value(fil.read(), b"changedit") + assert_value(fil.read(), b"yay") # create new file with CamelCase name ignorecase_dir["WeirdCase"].touch() @@ -182,6 +186,39 @@ def test_case_ignoring(root_path, root_dir): assert_value(root_path["A"].is_file(), True) +def test_append(root_path): + """ + Test the content append modes. + """ + + # create initial content + with root_path["appendfile"].open("wb") as fil: + fil.write(b"just") + + # append some data + with root_path["appendfile"].open("ab") as fil: + assert_value(fil.readable(), False) + assert_value(fil.writable(), True) + fil.write(b" some") + + # append some more data and then read it + with root_path["appendfile"].open("arb") as fil: + assert_value(fil.readable(), True) + assert_value(fil.writable(), True) + fil.write(b" test") + + # use the read-write mode to first read, then write, then read. + with root_path["appendfile"].open("rwb") as fil: + assert_value(fil.readable(), True) + assert_value(fil.writable(), True) + assert_value(fil.read(), b"just some test") + fil.seek(0) + fil.write(b"overwritten") + + fil.seek(0) + assert_value(fil.read(), b"overwrittenest") + + def test(): """ Perform functionality tests for the filesystem abstraction interface. @@ -201,6 +238,9 @@ def test(): # test the case ignoring dir test_case_ignoring(root_path, root_dir) + # test appending content + test_append(root_path) + # and remove all the things we just created assert_value(root_path.is_dir(), True) root_path.removerecursive()