1import io
2import sys
3from ctypes import Structure, POINTER, CFUNCTYPE, c_int, c_size_t, c_void_p, \
4    c_char_p, memmove, string_at, Union, _Pointer
5from .dll import _bind, version
6from .stdinc import Sint64, Uint8, Uint16, Uint32, Uint64, SDL_bool
7
8__all__ = [
9    # Structs
10    "SDL_RWops",
11
12    # Defines
13    "SDL_RWOPS_UNKNOWN", "SDL_RWOPS_WINFILE", "SDL_RWOPS_STDFILE",
14    "SDL_RWOPS_JNIFILE", "SDL_RWOPS_MEMORY", "SDL_RWOPS_MEMORY_RO",
15    "RW_SEEK_SET", "RW_SEEK_CUR", "RW_SEEK_END",
16
17    # Functions
18    "SDL_RWFromFile", "SDL_RWFromFP", "SDL_RWFromMem", "SDL_RWFromConstMem",
19    "SDL_AllocRW", "SDL_FreeRW", "SDL_RWsize", "SDL_RWseek",
20    "SDL_RWtell", "SDL_RWread", "SDL_RWwrite", "SDL_RWclose",
21    "SDL_LoadFile_RW", "SDL_LoadFile",
22    "SDL_ReadU8", "SDL_ReadLE16", "SDL_ReadBE16", "SDL_ReadLE32",
23    "SDL_ReadBE32", "SDL_ReadLE64", "SDL_ReadBE64", "SDL_WriteU8",
24    "SDL_WriteLE16", "SDL_WriteBE16", "SDL_WriteLE32", "SDL_WriteBE32",
25    "SDL_WriteLE64", "SDL_WriteBE64",
26
27    # Python Functions
28    "rw_from_object"
29]
30
31SDL_RWOPS_UNKNOWN = 0
32SDL_RWOPS_WINFILE = 1
33SDL_RWOPS_STDFILE = 2
34SDL_RWOPS_JNIFILE = 3
35SDL_RWOPS_MEMORY = 4
36SDL_RWOPS_MEMORY_RO = 5
37
38class SDL_RWops(Structure):
39    pass
40
41class _hidden(Union):
42    pass
43
44_sdlsize = CFUNCTYPE(Sint64, POINTER(SDL_RWops))
45_sdlseek = CFUNCTYPE(Sint64, POINTER(SDL_RWops), Sint64, c_int)
46_sdlread = CFUNCTYPE(c_size_t, POINTER(SDL_RWops), c_void_p, c_size_t, c_size_t)
47_sdlwrite = CFUNCTYPE(c_size_t, POINTER(SDL_RWops), c_void_p, c_size_t, c_size_t)
48_sdlclose = CFUNCTYPE(c_int, POINTER(SDL_RWops))
49SDL_RWops._fields_ = [("size", _sdlsize),
50                      ("seek", _sdlseek),
51                      ("read", _sdlread),
52                      ("write", _sdlwrite),
53                      ("close", _sdlclose),
54                      ("type", Uint32),
55                      ("hidden", _hidden)
56                      ]
57
58SDL_RWFromFile = _bind("SDL_RWFromFile", [c_char_p, c_char_p], POINTER(SDL_RWops))
59SDL_RWFromFP = _bind("SDL_RWFromFP", [c_void_p, SDL_bool], POINTER(SDL_RWops))
60SDL_RWFromMem = _bind("SDL_RWFromMem", [c_void_p, c_int], POINTER(SDL_RWops))
61SDL_RWFromConstMem = _bind("SDL_RWFromConstMem", [c_void_p, c_int], POINTER(SDL_RWops))
62SDL_AllocRW = _bind("SDL_AllocRW", None, POINTER(SDL_RWops))
63SDL_FreeRW = _bind("SDL_FreeRW", [POINTER(SDL_RWops)])
64
65SDL_LoadFile_RW = _bind("SDL_LoadFile_RW", [POINTER(SDL_RWops), POINTER(c_size_t), c_int], c_void_p, added='2.0.6')
66# SDL_LoadFile was a macro in SDL <= 2.0.9, added as a function in 2.0.10
67if version >= 2010:
68    SDL_LoadFile = _bind("SDL_LoadFile", [c_char_p, c_size_t], c_void_p)
69else:
70    SDL_LoadFile = lambda fname, ds: SDL_LoadFile_RW(SDL_RWFromFile(fname, "rb"), ds, 1)
71
72RW_SEEK_SET = 0
73RW_SEEK_CUR = 1
74RW_SEEK_END = 2
75
76def _ptr2obj(ptr):
77    """If a pointer, returns its contents. Otherwise, returns the passed object.
78    """
79    if isinstance(ptr, _Pointer):
80        return ptr.contents
81    return ptr
82
83# The following set of functions were macros in SDL <= 2.0.9 but became full
84# functions in SDL 2.0.10. Lambda functions are to mimic macro behaviour with
85# earlier SDL2 versions.
86if version >= 2010:
87    SDL_RWsize = _bind("SDL_RWsize", [POINTER(SDL_RWops)], Sint64)
88    SDL_RWseek = _bind("SDL_RWseek", [POINTER(SDL_RWops), Sint64, c_int], Sint64)
89    SDL_RWtell = _bind("SDL_RWtell", [POINTER(SDL_RWops)], Sint64)
90    SDL_RWread = _bind("SDL_RWread", [POINTER(SDL_RWops), c_void_p, c_size_t, c_size_t], c_size_t)
91    SDL_RWwrite = _bind("SDL_RWwrite", [POINTER(SDL_RWops), c_void_p, c_size_t, c_size_t], c_size_t)
92    SDL_RWclose = _bind("SDL_RWclose", [POINTER(SDL_RWops)], c_int)
93else:
94    _p = _ptr2obj # allow pointers to be passed directly to these functions
95    SDL_RWsize = lambda ctx: _p(ctx).size(_p(ctx))
96    SDL_RWseek = lambda ctx, offset, whence: _p(ctx).seek(_p(ctx), offset, whence)
97    SDL_RWtell = lambda ctx: _p(ctx).seek(_p(ctx), 0, RW_SEEK_CUR)
98    SDL_RWread = lambda ctx, ptr, size, n: _p(ctx).read(_p(ctx), ptr, size, n)
99    SDL_RWwrite = lambda ctx, ptr, size, n: _p(ctx).write(_p(ctx), ptr, size, n)
100    SDL_RWclose = lambda ctx: _p(ctx).close(_p(ctx))
101
102SDL_ReadU8 = _bind("SDL_ReadU8", [POINTER(SDL_RWops)], Uint8)
103SDL_ReadLE16 = _bind("SDL_ReadLE16", [POINTER(SDL_RWops)], Uint16)
104SDL_ReadBE16 = _bind("SDL_ReadBE16", [POINTER(SDL_RWops)], Uint16)
105SDL_ReadLE32 = _bind("SDL_ReadLE32", [POINTER(SDL_RWops)], Uint32)
106SDL_ReadBE32 = _bind("SDL_ReadBE32", [POINTER(SDL_RWops)], Uint32)
107SDL_ReadLE64 = _bind("SDL_ReadLE64", [POINTER(SDL_RWops)], Uint64)
108SDL_ReadBE64 = _bind("SDL_ReadBE64", [POINTER(SDL_RWops)], Uint64)
109
110SDL_WriteU8 = _bind("SDL_WriteU8", [POINTER(SDL_RWops), Uint8], c_size_t)
111SDL_WriteLE16 = _bind("SDL_WriteLE16", [POINTER(SDL_RWops), Uint16], c_size_t)
112SDL_WriteBE16 = _bind("SDL_WriteBE16", [POINTER(SDL_RWops), Uint16], c_size_t)
113SDL_WriteLE32 = _bind("SDL_WriteLE32", [POINTER(SDL_RWops), Uint32], c_size_t)
114SDL_WriteBE32 = _bind("SDL_WriteBE32", [POINTER(SDL_RWops), Uint32], c_size_t)
115SDL_WriteLE64 = _bind("SDL_WriteLE64", [POINTER(SDL_RWops), Uint64], c_size_t)
116SDL_WriteBE64 = _bind("SDL_WriteBE64", [POINTER(SDL_RWops), Uint64], c_size_t)
117
118if sys.version_info[0] >= 3:
119    try:
120	    from collections.abc import Callable
121    except ImportError:
122	    from collections import Callable
123    callable = lambda x: isinstance(x, Callable)
124
125def rw_from_object(obj):
126    """Creats a SDL_RWops from any Python object.
127
128    The Python object must at least support the following methods:
129
130        read(length) -> data
131            length is the size in bytes to be read. A call to len(data) must
132            return the correct amount of bytes for the data, so that
133            len(data) / [size in bytes for a single element from data] returns
134            the amount of elements.
135            Must raise an error on failure.
136
137        seek(offset, whence) -> int
138            offset denotes the offset to move the read/write pointer of the
139            object to. whence indicates the movement behaviour and can be one
140            of the following values:
141                RW_SEEK_SET - move to offset from the start of the file
142                RW_SEEK_CUR - move by offset from the relative location
143                RW_SEEK_END - move to offset from the end of the file
144            If it could not move read/write pointer to the desired location,
145            an error must be raised.
146
147        tell() -> int
148            Must return the current offset. This method must only be
149            provided, if seek() does not return any value.
150
151        close() -> None
152            Closes the object(or its internal data access methods). Must raise
153            an error on failure.
154
155        write(data) -> None
156            Writes the passed data(which is a string of bytes) to the object.
157            Must raise an error on failure.
158
159        Note: The write() method is optional and only necessary, if the passed
160        object should be able to write data.
161
162    The returned SDL_RWops is a pure Python object and must not be freed via
163    free_rw().
164    """
165    if not hasattr(obj, "read"):
166        raise TypeError("obj must have a read(len) -> data method")
167    if not hasattr(obj, "seek") or not callable(obj.seek):
168        raise TypeError("obj must have a seek(offset, whence) method")
169    if not hasattr(obj, "close") or not callable(obj.close):
170        raise TypeError("obj must have a close() -> int method")
171
172    rwops = SDL_RWops()
173
174    def _rwsize(context):
175        try:
176            if hasattr(obj, "size"):
177                if callable(obj.size):
178                    return obj.size()
179                else:
180                    return obj.size
181            else:
182                cur = obj.seek(0, RW_SEEK_CUR)
183                length = obj.seek(0, RW_SEEK_END)
184                obj.seek(cur, RW_SEEK_CUR)
185                return length
186        except Exception:
187            #print(e)
188            return -1
189    rwops.size = _sdlsize(_rwsize)
190
191    def _rwseek(context, offset, whence):
192        try:
193            retval = obj.seek(offset, whence)
194            if retval is None:
195                retval = obj.tell()
196            return retval
197        except Exception:
198            #print(e)
199            return -1
200    rwops.seek = _sdlseek(_rwseek)
201
202    def _rwread(context, ptr, size, maxnum):
203        try:
204            data = obj.read(size * maxnum)
205            num = len(data)
206            memmove(ptr, data, num)
207            return num // size
208        except Exception:
209            #print(e)
210            return 0
211    rwops.read = _sdlread(_rwread)
212
213    def _rwclose(context):
214        try:
215            retval = obj.close()
216            if retval is None:
217                # No return value; we assume that everything is okay.
218                return 0
219            return retval
220        except Exception:
221            #print(e)
222            return -1
223    rwops.close = _sdlclose(_rwclose)
224
225    def _rwwrite(context, ptr, size, num):
226        try:
227            # string_at feels wrong, since we access a raw byte buffer...
228            retval = obj.write(string_at(ptr, size * num))
229            if issubclass(type(obj), io.IOBase):
230                if retval is None: # Means write error
231                    return 0
232                return retval // size
233            # If not an io object, try to interpret retval as bytes written
234            # and, failing that, just assume success if no exception raised
235            # and return num
236            try:
237                return int(retval) // size
238            except TypeError:
239                return num
240        except Exception:
241            #print(e)
242            return 0
243
244    if hasattr(obj, "write") and callable(obj.write):
245        rwops.write = _sdlwrite(_rwwrite)
246    else:
247        rwops.write = _sdlwrite()
248    return rwops
249