Page 1 of 1

No module named 'termios' when using module "wget" in Android - Ren'Py

Posted: Sat Jan 23, 2021 10:13 pm
by CharlieFuu69
Hello!!

In advance I apologize for my level of English. Unfortunately it is not my native language.

Some time ago I asked for help in this forum regarding the Python "wget" module, which is used to execute downloads and show the download progress of files.
Since then I have been doing several experiments generating compilations of a game until the Android compilation stopped me.

When starting the download in Android, "wget" requests to import the module "termios", which returns the exception :

Code: Select all

No module named 'termios'.
I took a walk all over Google looking for a solution, but as you can already deduce, I was not successful.

I have Python v3.8 installed on my computer. I proceeded to import "termios" into the interpreter, which worked without a hitch. It is possible that this problem involves the engine over my project.

Regarding downloading files on Android, I'm using the "ANDROID _PUBLIC" (/0/Android/data/com.package.name/files/) path, and it works great when manipulating files from the game via the public path, so I completely rule out that the problem is due to the nature of Android.
NOTE: I am using Ren'Py version v7.4.0. I tested on v7.3.5 and v7.3.2 and the error persists.
I appreciate the time you give to read this problem =D

Re: No module named 'termios' when using module "wget" in Android - Ren'Py

Posted: Sat Jan 23, 2021 11:24 pm
by PyTom
I'd suggest using requests rather than wget. It's included with 7.4 and 7.4.1.

Re: No module named 'termios' when using module "wget" in Android - Ren'Py

Posted: Sun Jan 24, 2021 12:12 am
by CharlieFuu69
I also considered using the "requests" module as an option, but the issue of adding a progress bar for the download is complicated.
Since I have little experience in programming, some time ago I asked for help to create the download progress bar with "wget", in which I was given a base to implement this in Ren'Py.
This code is a reference to what I currently have implemented for downloads:

Code: Select all

init python:

    import threading
    import wget
    from os import path

    class DownloadControl(threading.Thread):

        def __init__(self, url, out=None):

            super(DownloadControl, self).__init__()
            self.daemon = True

            self.__url = url
            self.__out = out

            self.__download_status = None
            self.__is_finished = False

            self.__exception = None
            self.__result_filename = None

        @property
        def status(self):
            return (self.__download_status or .0)

        @property
        def _filename(self):
            return self.__result_filename

        def download_is_finished(self):
            return self.__is_finished

        def _has_exception(self):
            if isinstance(self.__exception, Exception):
                return True
            return False

        def _raise_from_thread(self):
            if self._has_exception():
                raise self.__exception

        def _callback_func(self, current, total, *args, **kwargs):
            current, total = map(float, (current, total))
            if total > .0:
                self.__download_status = (current / total)
                renpy.restart_interaction()

        def run(self):

            try:
                _result_fn = wget.download(
                    self.__url,
                    self.__out,
                    bar=self._callback_func
                )
            except Exception as ex:
                self.__exception = ex
                self.__download_status = .0
            else:
                self.__result_filename = path.abspath(_result_fn)
            finally:
                self.__is_finished = True
                renpy.restart_interaction()


screen download_screen(url, out=None):

    default download_thread = DownloadControl(url, out)
    default _download_is_started = False

    showif _download_is_started:
        showif download_thread.download_is_finished():
            showif download_thread._has_exception():
                vbox:
                    text "Error have occurred."
                    textbutton "Raise traceback." action Function(
                        download_thread._raise_from_thread
                    )
            else:
                vbox:
                    text "Download completed."
                    textbutton "Return" action Return()
        else:
            vbox:
                text "Download {0:.1%}.".format(download_thread.status)
                bar value AnimatedValue(download_thread.status, 1.)
    else:
        textbutton "Start download" action (
            Function(download_thread.start),
            SetScreenVariable("_download_is_started", True)
        )


label start:
    call screen download_screen("https://www.renpy.org/dl/7.3.5/renpy-7.3.5-sdk.7z.exe")
    return
As wget offers in advance the possibility to show the download progress, this would make things much easier, but I don't know exactly how to implement the same progress bar using "requests".
How could I show a download progress bar (indicating the total percentage downloaded) in the Ren'Py environment?

Re: No module named 'termios' when using module "wget" in Android - Ren'Py

Posted: Sun Jan 24, 2021 1:30 am
by PyTom
You probably want requests.get(url, stream=True), and then response.iter_content().

Re: No module named 'termios' when using module "wget" in Android - Ren'Py

Posted: Wed Jan 27, 2021 5:37 am
by CharlieFuu69
I appreciate your help PyTom!!! I implemented both systems in my game (using "wget" for Windows version and "requests" for Android.

For those who dare to use "wget" on Android, I have found the solution to prevent the module from throwing the exception no module named 'termios'.
I started looking closely at the wget code (wget.py) until I found the exact point that asks to import "termios". You can definitely use wget without the need to have "termios".
All of this leads me to this function:

Code: Select all

def get_console_width():
    """Return width of available window area. Autodetection works for
       Windows and POSIX platforms. Returns 80 for others

       Code from http://bitbucket.org/techtonik/python-pager
    """

    if os.name == 'nt':
        STD_INPUT_HANDLE  = -10
        STD_OUTPUT_HANDLE = -11
        STD_ERROR_HANDLE  = -12

        # get console handle
        from ctypes import windll, Structure, byref
        try:
            from ctypes.wintypes import SHORT, WORD, DWORD
        except ImportError:
            # workaround for missing types in Python 2.5
            from ctypes import (
                c_short as SHORT, c_ushort as WORD, c_ulong as DWORD)
        console_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)

        # CONSOLE_SCREEN_BUFFER_INFO Structure
        class COORD(Structure):
            _fields_ = [("X", SHORT), ("Y", SHORT)]

        class SMALL_RECT(Structure):
            _fields_ = [("Left", SHORT), ("Top", SHORT),
                        ("Right", SHORT), ("Bottom", SHORT)]

        class CONSOLE_SCREEN_BUFFER_INFO(Structure):
            _fields_ = [("dwSize", COORD),
                        ("dwCursorPosition", COORD),
                        ("wAttributes", WORD),
                        ("srWindow", SMALL_RECT),
                        ("dwMaximumWindowSize", DWORD)]

        sbi = CONSOLE_SCREEN_BUFFER_INFO()
        ret = windll.kernel32.GetConsoleScreenBufferInfo(
            console_handle, byref(sbi))
        if ret == 0:
            return 0
        return sbi.srWindow.Right+1

    elif os.name == 'posix':
        from fcntl import ioctl
        from termios import TIOCGWINSZ
        from array import array

        winsize = array("H", [0] * 4)
        try:
            ioctl(sys.stdout.fileno(), TIOCGWINSZ, winsize)
        except IOError:
            pass
        return (winsize[1], winsize[0])[0]

    return 80
This function returns a value to define the width of the window, in order to show a progress bar in the console / terminal / interpreter.
This is somewhat irrelevant to the Ren'Py code that I am using at the moment.
Just pay attention to what the text at the beginning of the function tells us:

Code: Select all

"""Return width of available window area. Autodetection works for
   Windows and POSIX platforms. Returns 80 for others

   Code from http://bitbucket.org/techtonik/python-pager
"""
When using "wget" in Android, it detects Android as a POSIX platform, which is completely natural if you understand the architecture of Android.
This function returns 80 when neither platform (NT or POSIX) is detected.
A simple change that will make "wget" work, would be to eliminate much of the code in this function, making it always return the value 80 when it is called. The function that I showed previously, should simply look like this:

Code: Select all

def get_console_width():
    """Return width of available window area. Autodetection works for
       Windows and POSIX platforms. Returns 80 for others

       Code from http://bitbucket.org/techtonik/python-pager
    """

    return 80
This does not affect the main operation of the module or the code that I implemented in Ren'Py (with the help of more people). Downloads are successful and the progress bar in Ren'Py continues to work as expected.
Hope this helps other folks experimenting with Android downloads using "wget". My main intention to implement the downloads on Android was so that the player, at the end of the game, can obtain DLCs and "patch" the game through the Android public path.