# Source code for gio_pyio

    """gio_pyio lib."""
    import io
    import os

    from gi.repository import GLib, Gio

    class StreamWrapper(io.IOBase):
        """Wrap a stream as a `file object`_.

        See :func:`Gio.open_file_like` for a convenience method to open a file as a
        `file object`_. Note, that this does not implement buffering, seeking, etc.
        and relies on the capabilities of *stream*.

        :param stream stream:
            A stream to be wrapped.
        :raises TypeError:
            Invalid argument.

        .. _file object: https://docs.python.org/3/glossary.html#term-file-object
        """

        def __init__(self, stream):
            if isinstance(stream, Gio.InputStream):
                self._input_stream = stream
                self._output_stream = None
                self._ref_stream = stream
            elif isinstance(stream, Gio.OutputStream):
                self._input_stream = None
                self._output_stream = stream
                self._ref_stream = stream
            elif isinstance(stream, Gio.IOStream):
                # For some methods, we assume, both stream represent the same
                # object as well as being in sync in regards to seeking, that way
                # we don't need to duplicate logic for most methods.
                self._input_stream = stream.get_input_stream()
                self._output_stream = stream.get_output_stream()
                self._ref_stream = self._input_stream
                # Keep a reference, or the stream might get closed
                self._io_stream = stream
            else:
                raise TypeError('expected stream, got %s' % type(stream))

        def close(self):
            """Flush and close the underlying stream.

            This method has no effect if the underlying stream is already closed.
            Once closed, any operation (e. g. reading or writing) will raise a
            ValueError.

            As a convenience, it is allowed to call this method more than once;
            only the first call, however, will have an effect.
            """
            if hasattr(self, '_io_stream'):
                self._io_stream.close()
                return
            if self.readable():
                self._input_stream.close()
            if self.writable():
                self._output_stream.close()

        @property
        def closed(self):
            """``True`` if the underlying stream is closed."""
            return self._ref_stream.is_closed()

        def fileno(self):
            """Return the underlying file descriptor if it exists.

            :rtype: int
            :returns:
                The underlying file descriptor.
            :raises ValueError:
                If the underlying stream is closed.
            :raises io.UnsupportedOperationException:
                If the underlying stream is not based on a file descriptor.
            """
            self._checkClosed()
            if not (self._ref_stream, 'get_fd'):
                self._unsupported('fileno')
            return self._ref_stream.get_fd()

        def flush(self):
            """Flush the write buffers of the underlying stream if applicable.

            This does nothing for read-only streams.

            :raises ValueError:
                If the underlying stream is closed.
            """
            self._checkClosed()
            if self.writable():
                self._output_stream.flush(None)

        def readable(self):
            """Whether or not the stream is readable.

            :rtype bool:
            :returns:
                Whether or not this wrapper can be read from.
            """
            return self._input_stream is not None

        def read(self, size=-1):
            """Read up to *size* bytes from the underlying stream and return them.

            As a convenience if *size* is unspecified or -1, all bytes until EOF
            are returned. The result may be fewer bytes than requested, if EOF is
            reached.

            :param int size:
                The amount of bytes to read from the underlying stream.
            :rtype: bytes
            :returns:
                Bytes read from the underlying stream.
            :raises ValueError:
                If the underlying stream is closed.
            :raises io.UnsupportedOperationException:
                If the underlying stream is not readable.
            """
            self._checkClosed()
            self._checkReadable()
            if size == 0:
                return b''
            elif size > 0:
                return self._input_stream.read_bytes(size, None).get_data()
            # Try to determine the length of the stream, else fall back on
            # default buffer size for reading
            if isinstance(self._input_stream, Gio.BufferedInputStream):
                def_bufsize = self._input_stream.get_buffer_size()
            else:
                def_bufsize = io.DEFAULT_BUFFER_SIZE
                end = None
                if hasattr(self._input_stream, 'get_size'):
                    end = self._input_stream.get_size()
                elif hasattr(self._input_stream, 'query_info'):
                    info = self._input_stream.query_info('standard::size', None)
                    end = info.get_size()
                if end is not None:
                    pos = self._ref_stream.tell()
                    if end >= pos:
                        bufsize = end - pos + 1
                else:
                    bufsize = def_bufsize
            result = bytearray()
            while True:
                if len(result) >= bufsize:
                    bufsize = len(result)
                    bufsize += max(bufsize, def_bufsize)
                n = bufsize - len(result)
                chunk = self._input_stream.read_bytes(n, None)
                if chunk.get_size() == 0:  # EOF reached
                    break
                result += chunk.get_data()
            return bytes(result)

        read1 = read
        readall = read

        def readinto(self, b):
            """Read bytes into a pre-allocated, writable `bytes-like object`_ *b*.

            :param bytes-like b:
                A pre-allocated object.
            :rtype: int
            :returns:
                Number of bytes written.
            :raises ValueError:
                If the underlying stream is closed.
            :raises io.UnsupportedOperationException:
                If the underlying stream is not readable.

            .. _bytes-like object: https://docs.python.org/3/glossary.html#term-bytes-like-object
            """
            self._checkClosed()
            self._checkReadable()
            view = memoryview(b).cast('B')
            data = self._input_stream.read_bytes(len(view), None)
            size = data.get_size()
            view[:size] = data.get_data()
            return size

        readinto1 = readinto

        def seek(self, offset, whence=os.SEEK_SET):
            """Change the underlying stream position.

            *offset* is interpreted relative to the position indicated by *whence*.

            :param int offset:
                Where to change the stream position to, relative to *whence*
            :param int whence:
                Reference for *offset*. Values are:
                * 0 -- start of stream (the default); offset should'nt be negative
                * 1 -- current stream position; offset may be negative
                * 2 -- end of stream; offset is usually negative
            :rtype: int
            :returns:
                The new absolute position of the underlying stream.
            :raises ValueError:
                If the underlying stream is closed.
            :raises io.UnsupportedOperationException:
                If the underlying stream is not seekable.
            """
            self._checkClosed()
            self._checkSeekable()
            # Enum values in python and Gio:
            # 0: os.SEEK_SET : Gio.SeekType.CUR
            # 1: os.SEEK_CUR : Gio.SeekType.SET
            # 2: os.SEEK_END : Gio.SeekType.END
            # so 1 and 0 need to be switched
            if whence != 2:
                whence = not whence
            if self.readable():
                self._input_stream.seek(offset, whence, None)
            if self.writable():
                self._output_stream.seek(offset, whence, None)
            return self._ref_stream.tell()

        def seekable(self):
            """
            Whether or not the stream is seekable.

            :rtype: bool
            :returns:
                Whether or not the underlying stream supports seeking.
            :raises ValueError:
                If the underlying stream is closed.
            """
            self._checkClosed()
            return self._ref_stream.can_seek()

        def tell(self):
            """
            Tell the current stream position.

            :rtype: int
            :returns:
                The position of the underlying stream.
            :raises ValueError:
                If the underlying stream is closed.
            """
            self._checkClosed()
            return self._ref_stream.tell()

        def truncate(self, size=None):
            """Resize the underlying stream to *size*.

            :param int size:
                The size, the stream should be set to. If ``None`` the current
                position is used.
            :rtype: int
            :returns:
                The new size of the underlying stream.
            :raises ValueError:
                If the underlying stream is closed.
            :raises io.UnsupportedOperationException:
                If the underlying stream can not be written to.
            """
            self._checkClosed()
            if self._output_stream is None or \
                    not self._output_stream.can_truncate():
                raise io.UnsupportedOperation('truncate')
            if size is None:
                size = self._output_stream.tell()
            self._output_stream.truncate(size)
            return size

        def writable(self):
            """
            Wheter or not the stream can be written to.

            :rtype: bool
            :returns:
                Whether or not this wrapper can be written to.
            """
            return self._output_stream is not None

        def write(self, b):
            """Write *b* to the underlying stream.

            :param bytes-like b:
                Content to be written to the underlying stream.
            :rtype: int
            :returns:
                The number of bytes written to the underlying stream.
            :raises ValueError:
                If the underlying stream is closed.
            :raises io.UnsupportedOperationException:
                If the underlying stream can not be written to.
            """
            self._checkClosed()
            self._checkWritable()
            if b is None or b == b'':
                return 0
            return self._output_stream.write_bytes(GLib.Bytes(b))

    def open(file, mode='r', buffering=-1, encoding=None, errors=None,
             newline=None, native=True):
        r"""Open the file and create a corresponding `file object`_.

        If the file cannot be opened, an OSError is raised. This behaves analog to
        pythons builtin :external:py:func:`open` function. See
        `Reading and Writing Files`_ for examples of io using python.

        :param Gio.File file:
            The file to open.
        :param str mode:
            Mode in which the file is opened. Defaults to 'r' which means
            open for reading in text mode. Other common values are 'w' for
            writing (truncating the file if it already exists), 'x' for
            exclusive creation of a new file, and 'a' for appending. The
            available modes are:

            ========= ===================================================
            Character Meaning
            --------- ---------------------------------------------------
            'r'       open for reading (default)
            'w'       open for writing, truncating the file first
            'x'       create a new file and open it for writing
            'a'       open for writing, appending to the end of the file
            'b'       binary mode
            't'       text mode (default)
            '+'       open a disk file for updating (reading and writing)
            ========= ===================================================

            The default value is 'r' (open for reading text, a synonym of 'rt').
            For binary random access, the mode 'w+b' opens and truncates the
            file to 0 bytes, while 'r+b' opens the file without truncation. The
            'x' mode implies 'w' and raises an `FileExistsError` if the file
            already exists.

            Python distinguishes between files opened in binary and text mode.
            Files opened in binary mode (appending 'b' to the mode argument)
            return contents as bytes objects without any decoding. In text mode
            (the default, or when 't' is appended to the mode argument), the
            contents of the file are returned as strings, the bytes having
            first been decoded using *encoding*.
        :param int buffering:
            Buffering policy. Pass 0 to switch buffering off (only allowed in
            binary mode), 1 to select line buffering (only usable in text mode),
            and an integer > 1 to indicate the size of a fixed-size chunk
            buffer. When no buffering argument is given.

            Files are buffered in fixed-size chunks; the size of the buffer is
            chosen using a heuristic trying to determine the underlying device's
            blksize and falling back on `io.DEFAULT_BUFFER_SIZE`. The buffer
            will typically be 4096 or 8192 bytes long.
        :param str encoding:
            Encoding used to decode or encode the file. Can only be used in text
            mode. The default encoding is platform dependent, but any encoding
            supported by Python can be passed. See the codecs module for the
            list of supported encodings.
        :param str errors:
            How encoding errors are to be handled. Can not be used in binary
            mode. Pass 'strict' to raise a ValueError exception if there is an
            encoding error (the default of None has the same effect), or pass
            'ignore' to ignore errors. (Note that ignoring encoding errors can
            lead to data loss.) See the documentation for codecs.register for a
            list of the permitted encoding error strings.
        :param str newline:
            How universal newlines work (only applies to text mode). Can be
            None, '', '\n', '\r', and '\r\n'. Works as follows:

            * On input, if newline is None, universal newlines mode is enabled.
              Lines in the input can end in '\n', '\r', or '\r\n', and these are
              translated into '\n' before being returned to the caller. If it is
              '', universal newline mode is enabled, but line endings are
              returned to the caller untranslated. If it has any of the other
              legal values, input lines are only terminated by the given string,
              and the line ending is returned to the caller untranslated.

            * On output, if newline is None, any '\n' characters written are
              translated to the system default line separator, os.linesep. If
              newline is '', no translation takes place. If newline is any of
              the other legal values, any '\n' characters written are translated
              to the given string.
        :param bool native:
            Try and obtain a file descriptor and use python standard io libraries.
            If False, the result will always be a wrapped Gio stream.
        :rtype: file-like
        :returns:
            A new `file object`_. When used to open a file in a text mode ('w',
            'r', 'wt', 'rt', etc.), the object will be a TextIOWrapper. When
            used to open a file in a binary mode, the returned class varies:
            in read binary mode, it will be a BufferedReader; in write binary
            and append binary modes, it will be a BufferedWriter, and in
            read/write mode, it will be a BufferedRandom. If buffering is
            disabled, the object will either be a FileIO or
            :py:class:`StreamWrapper` depending on python native libraries can be
            used.
        :raises TypeError:
            Invalid argument passed.
        :raises ValueError:
            Invalid argument passed.
        :raises OSError:
            Failed to open file.

        .. _file object: https://docs.python.org/3/glossary.html#term-file-object
        .. _Reading and Writing Files: https://docs.python.org/3/tutorial/inputoutput.html#tut-files
        """
        # Argument validation
        if not isinstance(mode, str):
            raise TypeError('invalid mode: %r' % mode)
        if not isinstance(buffering, int):
            raise TypeError('invalid buffering: %r' % buffering)
        modes = set(mode)
        if modes - set('axrwb+t') or len(mode) > len(modes):
            raise ValueError('invalid mode: %r' % mode)
        if encoding is not None and not isinstance(encoding, str):
            raise TypeError('invalid encoding: %r' % encoding)
        if errors is not None and not isinstance(errors, str):
            raise TypeError('invalid errors: %r' % errors)
        creating = 'x' in modes
        reading = 'r' in modes
        writing = 'w' in modes
        appending = 'a' in modes
        updating = '+' in modes
        binary = 'b' in modes
        if binary and 't' in modes:
            raise ValueError("can't have text and binary mode at once")
        if creating + reading + writing + appending > 1:
            raise ValueError('must have exactly one of create/read/write/append'
                             ' mode')
        if not (creating or reading or writing or appending):
            raise ValueError('Must have exactly one of create/read/write/append'
                             ' mode and at most one plus')
        if binary and encoding is not None:
            raise ValueError("binary mode doesn't take an encoding argument")
        if binary and errors is not None:
            raise ValueError("binary mode doesn't take an errors argument")
        if binary and newline is not None:
            raise ValueError("binary mode doesn't take a newline argument")

        # For non-native files we use the result of `file.get_basename()`
        rep_str = file.peek_path() if file.is_native() else file.get_basename()
        file_type = file.query_file_type(Gio.FileQueryInfoFlags.NONE, None)
        if file_type == Gio.FileType.DIRECTORY:
            raise OSError(21, "Is a directory: '%s'" % rep_str)
        if file.query_exists():
            if creating:
                raise OSError(17, "File exists: '%s'" % rep_str)
        elif reading:
            raise OSError(2, "No such file or directory: '%s'" % rep_str)
        if buffering == 0 and not binary:
            raise ValueError("can't have unbuffered text I/O")

        if native and file.is_native():
            file_like = io.FileIO(
                file.peek_path(),
                (creating and 'x' or '') +
                (reading and 'r' or '') +
                (writing and 'w' or '') +
                (appending and 'a' or '') +
                (updating and '+' or ''),
            )
        else:
            stream = None
            # Match given mode to respective opener. All calls are non-async, thus
            # blocking as well as not cancellable.
            if updating:
                if creating:
                    stream = file.create_readwrite(Gio.FileCreateFlags.NONE, None)
                elif writing:
                    stream = file.replace_readwrite(None, False,
                                                    Gio.FileCreateFlags.NONE, None)
                else:
                    stream = file.open_readwrite(None)
            else:
                if creating:
                    stream = file.create(Gio.FileCreateFlags.NONE, None)
                elif reading:
                    stream = file.read(None)
                elif writing:
                    stream = file.replace(None, False, Gio.FileCreateFlags.NONE,
                                          None)
                elif appending:
                    stream = file.append_to(Gio.FileCreateFlags.NONE, None)

            # at this point stream should not be `None` or input validation has
            # failed substantially
            assert stream is not None
            file_like = StreamWrapper(stream)
        line_buffering = False
        if buffering != 0:
            if buffering == 1:
                buffering = -1
                line_buffering = True
            if buffering < 0:
                buffering = io.DEFAULT_BUFFER_SIZE
                try:
                    # Try to set buffersize to the blksize of the file system
                    blksize = os.fstat(file_like.fileno()).st_blksize
                    if blksize > 1:
                        buffering = blksize
                except (OSError, AttributeError):
                    pass
            if updating:
                wrapper = io.BufferedRandom
            elif reading:
                wrapper = io.BufferedReader
            else:
                wrapper = io.BufferedWriter
            file_like = wrapper(file_like, buffering)
        if not binary:
            file_like = io.TextIOWrapper(file_like, encoding=encoding,
                                         errors=errors, newline=newline,
                                         line_buffering=line_buffering)
            file_like.mode = mode
        return file_like