From 024dbb6ffacd24d6f73052822bcb8a6eb1f2a2a8 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Tue, 15 Oct 2024 21:17:40 +0300 Subject: [PATCH 01/15] A utility module has been created to work with android external storage files --- android/src/toga_android/libs/utilfile.py | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 android/src/toga_android/libs/utilfile.py diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py new file mode 100644 index 0000000000..b5253231ad --- /dev/null +++ b/android/src/toga_android/libs/utilfile.py @@ -0,0 +1,42 @@ +import io, abc +import java + +BufferedReader = java.jclass("java.io.BufferedReader") +InputStreamReader = java.jclass("java.io.InputStreamReader") +Objects = java.jclass("java.util.Objects") + + +class HandlerFileDialog(abc.ABC): + def __init__(self, parent, app_toga): + self.parent = parent + self.app = app_toga._impl + self.mActive = app_toga._impl.native + + @abc.abstractmethod + def show(self): + pass + + +class VFile(io.TextIOBase): #VirtualFile + def __init__(self, bufferedReader): + self._bfr = bufferedReader + + def __del__(self): + self.close() + + def close(self): + self._bfr.close() + + def readline(self, size=0): + return self._bfr.readLine() + + def read(self, size=0): + res = "" + counter = size if size else "+" + while counter: + resp = self._bfr.read() + if resp == -1: break + res += chr(resp) + if isinstance(counter, int): + counter -= 1 + return res From 23684aead43aedda76f91d7e5db6f7bbd450c198 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Tue, 15 Oct 2024 21:18:23 +0300 Subject: [PATCH 02/15] The file opening dialog is partially implemented --- android/src/toga_android/dialogs.py | 72 ++++++++++++++++++----------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/android/src/toga_android/dialogs.py b/android/src/toga_android/dialogs.py index 56c3366bd0..30ede9faec 100644 --- a/android/src/toga_android/dialogs.py +++ b/android/src/toga_android/dialogs.py @@ -1,10 +1,12 @@ from android import R from android.app import AlertDialog -from android.content import DialogInterface +from android.content import DialogInterface, Intent from java import dynamic_proxy import toga +from .libs import utilfile as uf + class OnClickListener(dynamic_proxy(DialogInterface.OnClickListener)): def __init__(self, fn=None, value=None): @@ -31,12 +33,12 @@ def show(self, host_window, future): class TextDialog(BaseDialog): def __init__( - self, - title, - message, - positive_text, - negative_text=None, - icon=None, + self, + title, + message, + positive_text, + negative_text=None, + icon=None, ): super().__init__() @@ -104,10 +106,10 @@ def __init__(self, title, message): class StackTraceDialog(BaseDialog): def __init__( - self, - title, - message, - **kwargs, + self, + title, + message, + **kwargs, ): super().__init__() @@ -117,11 +119,11 @@ def __init__( class SaveFileDialog(BaseDialog): def __init__( - self, - title, - filename, - initial_directory, - file_types=None, + self, + title, + filename, + initial_directory, + file_types=None, ): super().__init__() @@ -130,25 +132,39 @@ def __init__( class OpenFileDialog(BaseDialog): + class HandlerOpenDialog(uf.HandlerFileDialog): + def show(self): + intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.setType("*/*") + self.app.start_activity(intent, on_complete=self.parent.handler) + def __init__( - self, - title, - initial_directory, - file_types, - multiple_select, + self, + title, + initial_directory, + file_types, + multiple_select, ): - super().__init__() + self.native = OpenFileDialog.HandlerOpenDialog(self, toga.App.app.current_window) + self.mActive = self.native.mActive - toga.App.app.factory.not_implemented("dialogs.OpenFileDialog()") - self.native = None + def handler(self, code, indent): + self._handler(indent.getData()) + + def _handler(self, uri): + ist = self.mActive.getContentResolver().openInputStream(uri) + isr = uf.InputStreamReader(uf.Objects.requireNonNull(ist)) + reader = uf.VFile(uf.BufferedReader(isr)) + self.future.set_result(reader) class SelectFolderDialog(BaseDialog): def __init__( - self, - title, - initial_directory, - multiple_select, + self, + title, + initial_directory, + multiple_select, ): super().__init__() From 9b7af3660af6cf6e57652cc13bca8b65e448c664 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Tue, 15 Oct 2024 21:41:24 +0300 Subject: [PATCH 03/15] Create 2910.misc.rst --- changes/2910.misc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/2910.misc.rst diff --git a/changes/2910.misc.rst b/changes/2910.misc.rst new file mode 100644 index 0000000000..bda65f3df9 --- /dev/null +++ b/changes/2910.misc.rst @@ -0,0 +1 @@ +Partial OpenFileDialog implementation \ No newline at end of file From c4e245f678957b3eb78ae083b189a303ebf03d70 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Tue, 15 Oct 2024 21:51:18 +0300 Subject: [PATCH 04/15] Update utilfile.py --- android/src/toga_android/libs/utilfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index b5253231ad..03843b2213 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -1,4 +1,5 @@ -import io, abc +import io +import abc import java BufferedReader = java.jclass("java.io.BufferedReader") From e46c5df070b2a37da9bf2934cd26214af2c67602 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Tue, 15 Oct 2024 21:51:42 +0300 Subject: [PATCH 05/15] Update utilfile.py --- android/src/toga_android/libs/utilfile.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index 03843b2213..7815cc3dd7 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -1,5 +1,6 @@ -import io import abc +import io + import java BufferedReader = java.jclass("java.io.BufferedReader") @@ -18,7 +19,7 @@ def show(self): pass -class VFile(io.TextIOBase): #VirtualFile +class VFile(io.TextIOBase): # VirtualFile def __init__(self, bufferedReader): self._bfr = bufferedReader @@ -36,7 +37,8 @@ def read(self, size=0): counter = size if size else "+" while counter: resp = self._bfr.read() - if resp == -1: break + if resp == -1: + break res += chr(resp) if isinstance(counter, int): counter -= 1 From 0d66bbf3aaf2044c8ee2bb4526b1eae3689fcd4d Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Tue, 15 Oct 2024 22:03:26 +0300 Subject: [PATCH 06/15] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/src/toga_android/libs/utilfile.py | 19 ++++++++++++++++--- changes/2910.misc.rst | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index 7815cc3dd7..d326fe0c54 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -9,6 +9,8 @@ class HandlerFileDialog(abc.ABC): + """Абстрактный класс вызова файлового менеджера""" + def __init__(self, parent, app_toga): self.parent = parent self.app = app_toga._impl @@ -16,10 +18,13 @@ def __init__(self, parent, app_toga): @abc.abstractmethod def show(self): + """Запуск менеджера""" pass -class VFile(io.TextIOBase): # VirtualFile +class VFile(io.TextIOBase): + """Файл для работы с внешнем хранилищем андроида""" + def __init__(self, bufferedReader): self._bfr = bufferedReader @@ -29,10 +34,18 @@ def __del__(self): def close(self): self._bfr.close() - def readline(self, size=0): + def readline(self, size: int = 0) -> str: + """Функция для чтения строки + :param size: количество строк, которые нужно считать. + Если он равен 0, то будет прочтен весь файл + :return: Строка""" return self._bfr.readLine() - def read(self, size=0): + def read(self, size: int = 0) -> str: + """Функция для чтения строки + :param size: количество символов, которые нужно считать. + Если он равен 0, то будет прочтен весь файл + :return: Строка""" res = "" counter = size if size else "+" while counter: diff --git a/changes/2910.misc.rst b/changes/2910.misc.rst index bda65f3df9..cc4d12d4b5 100644 --- a/changes/2910.misc.rst +++ b/changes/2910.misc.rst @@ -1 +1 @@ -Partial OpenFileDialog implementation \ No newline at end of file +Partial OpenFileDialog implementation From f908fef9578fe3bc6cd00f37f0dd408883848736 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Tue, 15 Oct 2024 22:03:37 +0300 Subject: [PATCH 07/15] Update utilfile.py --- android/src/toga_android/libs/utilfile.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index d326fe0c54..8c3f821f22 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -36,16 +36,16 @@ def close(self): def readline(self, size: int = 0) -> str: """Функция для чтения строки - :param size: количество строк, которые нужно считать. - Если он равен 0, то будет прочтен весь файл - :return: Строка""" + :param size: количество строк, которые нужно считать. + Если он равен 0, то будет прочтен весь файл + :return: Строка""" return self._bfr.readLine() def read(self, size: int = 0) -> str: """Функция для чтения строки - :param size: количество символов, которые нужно считать. - Если он равен 0, то будет прочтен весь файл - :return: Строка""" + :param size: количество символов, которые нужно считать. + Если он равен 0, то будет прочтен весь файл + :return: Строка""" res = "" counter = size if size else "+" while counter: From f122867721ce3a3422b6cff2cf63418dfb8157ed Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Sun, 15 Dec 2024 21:14:02 +0300 Subject: [PATCH 08/15] Generalization implementation Generalization implementation of file interaction --- .../src/toga_android/managersFileSystem.py | 29 ++++++++++++++++ .../src/toga_winforms/managersFileSystem.py | 33 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 android/src/toga_android/managersFileSystem.py create mode 100644 winforms/src/toga_winforms/managersFileSystem.py diff --git a/android/src/toga_android/managersFileSystem.py b/android/src/toga_android/managersFileSystem.py new file mode 100644 index 0000000000..2365d35b6f --- /dev/null +++ b/android/src/toga_android/managersFileSystem.py @@ -0,0 +1,29 @@ +class Singtools(type): + _instance = None + + def __call__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__call__(cls, *args, **kwargs) + return cls._instance + + +class ManagerGlobal(metaclass=Singtools): + def __init__(self, app=None): + self._app = app + + +class BaseManager(metaclass=Singtools): + def getApp(self): + return ManagerGlobal()._app + + +class ManagerFile(BaseManager): + def open(self, path, mode="r", encoding="utf-8", new_line="\n"): + pass + + def delete(self, path): + pass + + +class ManagerFolder(BaseManager): + pass diff --git a/winforms/src/toga_winforms/managersFileSystem.py b/winforms/src/toga_winforms/managersFileSystem.py new file mode 100644 index 0000000000..c6503f93b9 --- /dev/null +++ b/winforms/src/toga_winforms/managersFileSystem.py @@ -0,0 +1,33 @@ +import os + + +class Singtools(type): + _instance = None + + def __call__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__call__(cls, *args, **kwargs) + return cls._instance + + +class ManagerGlobal(metaclass=Singtools): + def __init__(self, app=None): + self._app = app + + +class BaseManager(metaclass=Singtools): + def getApp(self): + return ManagerGlobal()._app + + +class ManagerFile(BaseManager): + + def open(self, path, mode="r", encoding="utf-8", new_line="\n", external=True): + return open(path, mode, encoding=encoding, newline=new_line) + + def delete(self, path): + os.remove(path) + + +class ManagerFolder: + pass From 132f09365dc06c0a7dfa9fd351206a1b7d25acb4 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Sun, 15 Dec 2024 21:28:54 +0300 Subject: [PATCH 09/15] Updating file system usage --- android/src/toga_android/dialogs.py | 61 +++--- android/src/toga_android/libs/utilfile.py | 181 +++++++++++++++--- .../src/toga_winforms/managersFileSystem.py | 2 +- 3 files changed, 183 insertions(+), 61 deletions(-) diff --git a/android/src/toga_android/dialogs.py b/android/src/toga_android/dialogs.py index 30ede9faec..72291a78fe 100644 --- a/android/src/toga_android/dialogs.py +++ b/android/src/toga_android/dialogs.py @@ -5,7 +5,7 @@ import toga -from .libs import utilfile as uf +from .libs import utilfile class OnClickListener(dynamic_proxy(DialogInterface.OnClickListener)): @@ -33,12 +33,12 @@ def show(self, host_window, future): class TextDialog(BaseDialog): def __init__( - self, - title, - message, - positive_text, - negative_text=None, - icon=None, + self, + title, + message, + positive_text, + negative_text=None, + icon=None, ): super().__init__() @@ -106,10 +106,10 @@ def __init__(self, title, message): class StackTraceDialog(BaseDialog): def __init__( - self, - title, - message, - **kwargs, + self, + title, + message, + **kwargs, ): super().__init__() @@ -119,11 +119,11 @@ def __init__( class SaveFileDialog(BaseDialog): def __init__( - self, - title, - filename, - initial_directory, - file_types=None, + self, + title, + filename, + initial_directory, + file_types=None, ): super().__init__() @@ -132,7 +132,7 @@ def __init__( class OpenFileDialog(BaseDialog): - class HandlerOpenDialog(uf.HandlerFileDialog): + class HandlerOpenDialog(utilfile.HandlerFileDialog): def show(self): intent = Intent(Intent.ACTION_OPEN_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) @@ -140,31 +140,32 @@ def show(self): self.app.start_activity(intent, on_complete=self.parent.handler) def __init__( - self, - title, - initial_directory, - file_types, - multiple_select, + self, + title, + initial_directory, + file_types, + multiple_select, ): - self.native = OpenFileDialog.HandlerOpenDialog(self, toga.App.app.current_window) + self.native = OpenFileDialog.HandlerOpenDialog( + self, toga.App.app.current_window + ) self.mActive = self.native.mActive def handler(self, code, indent): self._handler(indent.getData()) def _handler(self, uri): - ist = self.mActive.getContentResolver().openInputStream(uri) - isr = uf.InputStreamReader(uf.Objects.requireNonNull(ist)) - reader = uf.VFile(uf.BufferedReader(isr)) + inputStream = self.mActive.getContentResolver().openInputStream(uri) + reader = utilfile.PathReader(self.native.app, inputStream) self.future.set_result(reader) class SelectFolderDialog(BaseDialog): def __init__( - self, - title, - initial_directory, - multiple_select, + self, + title, + initial_directory, + multiple_select, ): super().__init__() diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index 8c3f821f22..acadfcf140 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -1,15 +1,13 @@ import abc +import asyncio as aio import io -import java - -BufferedReader = java.jclass("java.io.BufferedReader") -InputStreamReader = java.jclass("java.io.InputStreamReader") -Objects = java.jclass("java.util.Objects") +from java.io import BufferedReader, InputStreamReader, OutputStreamWriter +from java.util import Objects class HandlerFileDialog(abc.ABC): - """Абстрактный класс вызова файлового менеджера""" + """An abstract class that handles file manager calls""" def __init__(self, parent, app_toga): self.parent = parent @@ -22,37 +20,160 @@ def show(self): pass -class VFile(io.TextIOBase): - """Файл для работы с внешнем хранилищем андроида""" +class BasePath(io.TextIOBase, abc.ABC): + modes = [] + + def __init__(self, app, stream): + self.aloop: aio.AbstractEventLoop = app.loop + self._stream = stream + self._buffer = None + self._is_binary = False + self._new_line_int = 10 + self._encoding = "utf-8" + + def check_open(self): + """The defense mechanism on the open is that path""" + if self._buffer is None: + raise TypeError("File not open!") - def __init__(self, bufferedReader): - self._bfr = bufferedReader + def __enter__(self): + return self - def __del__(self): + def __exit__(self, exc_type, exc_val, exc_tb): self.close() def close(self): - self._bfr.close() - - def readline(self, size: int = 0) -> str: - """Функция для чтения строки - :param size: количество строк, которые нужно считать. - Если он равен 0, то будет прочтен весь файл - :return: Строка""" - return self._bfr.readLine() - - def read(self, size: int = 0) -> str: - """Функция для чтения строки - :param size: количество символов, которые нужно считать. - Если он равен 0, то будет прочтен весь файл - :return: Строка""" - res = "" - counter = size if size else "+" + self._stream.close() + if self._buffer is not None: + self._buffer.close() + + @abc.abstractmethod + def open(self, mode, encoding="utf-8", new_line="\n"): + """Method for opening a file by path""" + self._encoding = encoding + self._new_line_int = new_line.encode(encoding)[0] + if mode not in self.modes: + raise ValueError( + f"invalid mode {mode}.\nIt is allowed to use the following modes: R or RB" + ) + if "b" in mode: + self._is_binary = True + + +class BasePathReader(BasePath, abc.ABC): + modes = ["r", "rb"] + + def open(self, mode="r", encoding="utf-8", new_line="\n"): + """A method for opening a file for reading along the path""" + super().open(mode.lower(), encoding, new_line) + input_stream_reader = InputStreamReader(Objects.requireNonNull(self._stream)) + self._buffer = BufferedReader(input_stream_reader) + + @abc.abstractmethod + def read(self, size=0): + pass + + @abc.abstractmethod + def readline(self, size=0): + pass + + @abc.abstractmethod + def aread(self, size=0): + pass + + @abc.abstractmethod + def areadline(self, size=0): + pass + + +class PathReader(BasePathReader): + """A file-like object for reading the contents of an android external storage file""" + + def readline(self, size: int = 0) -> str | bytes: + """A function for reading lines from a file separated by new_line + :param size: the number of rows to be counted.(Currently not implemented, added for future implementation) + :return: Data type str[bytes](Depends on whether the flag and was passed) or the list of str[bytes] + """ + self.check_open() + res = bytearray() + counter = size if size else "-1" + while counter: + resp: int = self._buffer.read() + if resp == -1: + break + if resp == self._new_line_int: + break + res.append(resp) + if isinstance(counter, int): + counter -= 1 + if self._is_binary: + return bytes(res) + return res.decode(self._encoding) + + def read(self, size: int = 0) -> bytes | str: + """A function for reading a string + :param size: the number of characters to be counted. If it is equal to 0, the entire file will be read + :return: Data type str[bytes](depends on whether the 'b' flag was passed)""" + self.check_open() + res = bytearray() + counter = size if size else "-1" + while counter: + resp: int = self._buffer.read() + if resp == -1: + break + res.append(resp) + if isinstance(counter, int): + counter -= 1 + if self._is_binary: + return bytes(res) + return res.decode(self._encoding) + + async def aread(self, size=0): + """A function for reading a string + :param size: the number of characters to be counted. If it is equal to 0, the entire file will be read + :return: Data type str[bytes](depends on whether the 'b' flag was passed)""" + self.check_open() + res = bytearray() + counter = size if size else "-1" while counter: - resp = self._bfr.read() + resp: int = await self.aloop.run_in_executor(None, self._buffer.read) if resp == -1: break - res += chr(resp) + res.append(resp) if isinstance(counter, int): counter -= 1 - return res + if self._is_binary: + return bytes(res) + return res.decode(self._encoding) + + def areadline(self, size=0): + return NotImplemented + + +class BasePathWriter(BasePath, abc.ABC): + modes = ["w", "wb"] + + def open(self, mode="r", encoding="utf-8", new_line="\n"): + """A method for opening a file for writing along the path""" + super().open(mode.lower(), encoding, new_line) + self._buffer = OutputStreamWriter(self._stream) + + def _convertion_type_data(self, data): + if isinstance(data, bytes) and not self._is_binary: + return data.decode(self._encoding) + if isinstance(data, str) and self._is_binary: + return data.encode(self._encoding) + return data + + @abc.abstractmethod + def write(self, text: str | bytes): + data = self._convertion_type_data(text) + self._buffer.write(data) + + @abc.abstractmethod + def awrite(self, text): + pass + + +class PathWriter(BasePathReader): + pass diff --git a/winforms/src/toga_winforms/managersFileSystem.py b/winforms/src/toga_winforms/managersFileSystem.py index c6503f93b9..f55203df43 100644 --- a/winforms/src/toga_winforms/managersFileSystem.py +++ b/winforms/src/toga_winforms/managersFileSystem.py @@ -29,5 +29,5 @@ def delete(self, path): os.remove(path) -class ManagerFolder: +class ManagerFolder(BaseManager): pass From 840bbbb21b710f56260e322cd3c704c69e148688 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Sun, 15 Dec 2024 21:36:25 +0300 Subject: [PATCH 10/15] Update utilfile.py --- android/src/toga_android/libs/utilfile.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index acadfcf140..cb204a34bc 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -54,7 +54,8 @@ def open(self, mode, encoding="utf-8", new_line="\n"): self._new_line_int = new_line.encode(encoding)[0] if mode not in self.modes: raise ValueError( - f"invalid mode {mode}.\nIt is allowed to use the following modes: R or RB" + f"""invalid mode {mode}. + It is allowed to use the following modes: R or RB""" ) if "b" in mode: self._is_binary = True @@ -87,11 +88,13 @@ def areadline(self, size=0): class PathReader(BasePathReader): - """A file-like object for reading the contents of an android external storage file""" + """A phalloid object for reading. +Reads the contents of the Android external storage file""" def readline(self, size: int = 0) -> str | bytes: """A function for reading lines from a file separated by new_line - :param size: the number of rows to be counted.(Currently not implemented, added for future implementation) + :param size: the number of rows to be counted. + (Currently not implemented, added for future implementation) :return: Data type str[bytes](Depends on whether the flag and was passed) or the list of str[bytes] """ self.check_open() From 31bd71d52733ae092381827cbad53f7908a6c6c1 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Sun, 15 Dec 2024 21:39:02 +0300 Subject: [PATCH 11/15] Update utilfile.py --- android/src/toga_android/libs/utilfile.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index cb204a34bc..508473a739 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -89,13 +89,14 @@ def areadline(self, size=0): class PathReader(BasePathReader): """A phalloid object for reading. -Reads the contents of the Android external storage file""" + Reads the contents of the Android external storage file""" def readline(self, size: int = 0) -> str | bytes: - """A function for reading lines from a file separated by new_line + """A function for reading lines from a file :param size: the number of rows to be counted. (Currently not implemented, added for future implementation) - :return: Data type str[bytes](Depends on whether the flag and was passed) or the list of str[bytes] + :return: Data type str[bytes] + (Depends on whether the flag and was passed) or the list of str[bytes] """ self.check_open() res = bytearray() @@ -115,8 +116,10 @@ def readline(self, size: int = 0) -> str | bytes: def read(self, size: int = 0) -> bytes | str: """A function for reading a string - :param size: the number of characters to be counted. If it is equal to 0, the entire file will be read - :return: Data type str[bytes](depends on whether the 'b' flag was passed)""" + :param size: the number of characters to be counted. + If it is equal to 0, the entire file will be read + :return: Data type str[bytes] + (depends on whether the 'b' flag was passed)""" self.check_open() res = bytearray() counter = size if size else "-1" @@ -133,8 +136,10 @@ def read(self, size: int = 0) -> bytes | str: async def aread(self, size=0): """A function for reading a string - :param size: the number of characters to be counted. If it is equal to 0, the entire file will be read - :return: Data type str[bytes](depends on whether the 'b' flag was passed)""" + :param size: the number of characters to be counted. + If it is equal to 0, the entire file will be read + :return: Data type str[bytes] + (depends on whether the 'b' flag was passed)""" self.check_open() res = bytearray() counter = size if size else "-1" From ba66a0c737a9ef76876154349b849f9589ad0439 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Sun, 15 Dec 2024 21:50:29 +0300 Subject: [PATCH 12/15] Update 2910.misc.rst --- changes/2910.misc.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/changes/2910.misc.rst b/changes/2910.misc.rst index cc4d12d4b5..af638fb320 100644 --- a/changes/2910.misc.rst +++ b/changes/2910.misc.rst @@ -1 +1,7 @@ -Partial OpenFileDialog implementation +Android implementation of OpenFileDialog + +Adding a utility module for reading from external storage files + +Adding singleton for file management on Android/Windows for API communication + +Support with Python 3.10+ From 4bc9ed04afd5944d66dea5d22f32978325a65e5b Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Sun, 15 Dec 2024 22:11:01 +0300 Subject: [PATCH 13/15] Update utilfile.py --- android/src/toga_android/libs/utilfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index 508473a739..28806e2d30 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -11,8 +11,8 @@ class HandlerFileDialog(abc.ABC): def __init__(self, parent, app_toga): self.parent = parent - self.app = app_toga._impl - self.mActive = app_toga._impl.native + self.app = app_toga._impl.app + self.mActive = app_toga._impl.app.native @abc.abstractmethod def show(self): From cb321c1b27782078082d65d33cfa89dfaf844ead Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Thu, 19 Dec 2024 22:50:31 +0300 Subject: [PATCH 14/15] update toga_android MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Изменение чтобы сосстествало общему API --- android/src/toga_android/dialogs.py | 42 +++++--- android/src/toga_android/libs/utilfile.py | 95 +++++++++++-------- .../src/toga_android/managersFileSystem.py | 29 ------ changes/{2910.misc.rst => 2910.feature.rst} | 0 .../src/toga_winforms/managersFileSystem.py | 33 ------- 5 files changed, 85 insertions(+), 114 deletions(-) delete mode 100644 android/src/toga_android/managersFileSystem.py rename changes/{2910.misc.rst => 2910.feature.rst} (100%) delete mode 100644 winforms/src/toga_winforms/managersFileSystem.py diff --git a/android/src/toga_android/dialogs.py b/android/src/toga_android/dialogs.py index 72291a78fe..c7524cb8cd 100644 --- a/android/src/toga_android/dialogs.py +++ b/android/src/toga_android/dialogs.py @@ -1,3 +1,5 @@ +import abc + from android import R from android.app import AlertDialog from android.content import DialogInterface, Intent @@ -117,6 +119,20 @@ def __init__( self.native = None +class HandlerFileDialog(abc.ABC): + """An abstract class that handles file manager calls""" + + def __init__(self, parent): + self.parent = parent + self.app = toga.App.app._impl + self.mActive = toga.App.app._impl.native + + @abc.abstractmethod + def show(self): + """Запуск менеджера""" + pass + + class SaveFileDialog(BaseDialog): def __init__( self, @@ -131,13 +147,15 @@ def __init__( self.native = None +class HandlerOpenDialog(HandlerFileDialog): + def show(self): + intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.setType("*/*") + self.app.start_activity(intent, on_complete=self.parent.handler) + + class OpenFileDialog(BaseDialog): - class HandlerOpenDialog(utilfile.HandlerFileDialog): - def show(self): - intent = Intent(Intent.ACTION_OPEN_DOCUMENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.setType("*/*") - self.app.start_activity(intent, on_complete=self.parent.handler) def __init__( self, @@ -146,17 +164,13 @@ def __init__( file_types, multiple_select, ): - self.native = OpenFileDialog.HandlerOpenDialog( - self, toga.App.app.current_window - ) + self.native = HandlerOpenDialog(self) self.mActive = self.native.mActive def handler(self, code, indent): - self._handler(indent.getData()) - - def _handler(self, uri): - inputStream = self.mActive.getContentResolver().openInputStream(uri) - reader = utilfile.PathReader(self.native.app, inputStream) + uri = indent.getData() + content = self.mActive.getContentResolver() + reader = utilfile.PathReader(content, uri) self.future.set_result(reader) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index 28806e2d30..564891fb3f 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -1,35 +1,22 @@ import abc import asyncio as aio import io +import os from java.io import BufferedReader, InputStreamReader, OutputStreamWriter from java.util import Objects +import toga -class HandlerFileDialog(abc.ABC): - """An abstract class that handles file manager calls""" - def __init__(self, parent, app_toga): - self.parent = parent - self.app = app_toga._impl.app - self.mActive = app_toga._impl.app.native - - @abc.abstractmethod - def show(self): - """Запуск менеджера""" - pass - - -class BasePath(io.TextIOBase, abc.ABC): - modes = [] - - def __init__(self, app, stream): - self.aloop: aio.AbstractEventLoop = app.loop +class BaseFile(io.TextIOBase): + def __init__(self, stream, binary, encoding, newLineInt): + self.aloop: aio.AbstractEventLoop = toga.App.app._impl self._stream = stream self._buffer = None - self._is_binary = False - self._new_line_int = 10 - self._encoding = "utf-8" + self._is_binary = binary + self._new_line_int = newLineInt + self._encoding = encoding def check_open(self): """The defense mechanism on the open is that path""" @@ -47,26 +34,33 @@ def close(self): if self._buffer is not None: self._buffer.close() + +class BasePath(os.PathLike): + modes = [] + file_impl = None + + def __init__(self, content, uri): + self._uri = uri + self._content = content + self._stream = None + @abc.abstractmethod def open(self, mode, encoding="utf-8", new_line="\n"): """Method for opening a file by path""" - self._encoding = encoding - self._new_line_int = new_line.encode(encoding)[0] + _new_line_int = new_line.encode(encoding)[0] if mode not in self.modes: raise ValueError( f"""invalid mode {mode}. It is allowed to use the following modes: R or RB""" ) - if "b" in mode: - self._is_binary = True + _is_binary = "b" in mode + return self.file_impl(self._stream, _is_binary, encoding, _new_line_int) -class BasePathReader(BasePath, abc.ABC): - modes = ["r", "rb"] +class BaseFileReader(BaseFile, abc.ABC): - def open(self, mode="r", encoding="utf-8", new_line="\n"): - """A method for opening a file for reading along the path""" - super().open(mode.lower(), encoding, new_line) + def __init__(self, stream, binary, encoding, newLineInt): + super().__init__(stream, binary, encoding, newLineInt) input_stream_reader = InputStreamReader(Objects.requireNonNull(self._stream)) self._buffer = BufferedReader(input_stream_reader) @@ -87,7 +81,7 @@ def areadline(self, size=0): pass -class PathReader(BasePathReader): +class FileReader(BaseFileReader): """A phalloid object for reading. Reads the contents of the Android external storage file""" @@ -158,12 +152,24 @@ def areadline(self, size=0): return NotImplemented -class BasePathWriter(BasePath, abc.ABC): - modes = ["w", "wb"] +class PathReader(BasePath): + + modes = ["r", "rb"] + file_impl = FileReader def open(self, mode="r", encoding="utf-8", new_line="\n"): - """A method for opening a file for writing along the path""" - super().open(mode.lower(), encoding, new_line) + """A method for opening a file for reading along the path""" + self._stream = self._content.openInputStream(self._uri) + return super().open(mode.lower(), encoding, new_line) + + def __fspath__(self): + return str(self._uri) + + +class BaseFileWriter(BaseFile, abc.ABC): + + def __init__(self, stream, binary, encoding, newLineInt): + super().__init__(stream, binary, encoding, newLineInt) self._buffer = OutputStreamWriter(self._stream) def _convertion_type_data(self, data): @@ -174,14 +180,27 @@ def _convertion_type_data(self, data): return data @abc.abstractmethod + def write(self, text: str | bytes): + pass + + @abc.abstractmethod + def awrite(self, text): + pass + + +class FileWriter(BaseFileWriter): def write(self, text: str | bytes): data = self._convertion_type_data(text) self._buffer.write(data) - @abc.abstractmethod def awrite(self, text): pass -class PathWriter(BasePathReader): - pass +class PathWriter(BasePath, abc.ABC): + modes = ["w", "wb"] + file_impl = FileWriter + + def open(self, mode="r", encoding="utf-8", new_line="\n"): + """A method for opening a file for writing along the path""" + return super().open(mode.lower(), encoding, new_line) diff --git a/android/src/toga_android/managersFileSystem.py b/android/src/toga_android/managersFileSystem.py deleted file mode 100644 index 2365d35b6f..0000000000 --- a/android/src/toga_android/managersFileSystem.py +++ /dev/null @@ -1,29 +0,0 @@ -class Singtools(type): - _instance = None - - def __call__(cls, *args, **kwargs): - if cls._instance is None: - cls._instance = super().__call__(cls, *args, **kwargs) - return cls._instance - - -class ManagerGlobal(metaclass=Singtools): - def __init__(self, app=None): - self._app = app - - -class BaseManager(metaclass=Singtools): - def getApp(self): - return ManagerGlobal()._app - - -class ManagerFile(BaseManager): - def open(self, path, mode="r", encoding="utf-8", new_line="\n"): - pass - - def delete(self, path): - pass - - -class ManagerFolder(BaseManager): - pass diff --git a/changes/2910.misc.rst b/changes/2910.feature.rst similarity index 100% rename from changes/2910.misc.rst rename to changes/2910.feature.rst diff --git a/winforms/src/toga_winforms/managersFileSystem.py b/winforms/src/toga_winforms/managersFileSystem.py deleted file mode 100644 index f55203df43..0000000000 --- a/winforms/src/toga_winforms/managersFileSystem.py +++ /dev/null @@ -1,33 +0,0 @@ -import os - - -class Singtools(type): - _instance = None - - def __call__(cls, *args, **kwargs): - if cls._instance is None: - cls._instance = super().__call__(cls, *args, **kwargs) - return cls._instance - - -class ManagerGlobal(metaclass=Singtools): - def __init__(self, app=None): - self._app = app - - -class BaseManager(metaclass=Singtools): - def getApp(self): - return ManagerGlobal()._app - - -class ManagerFile(BaseManager): - - def open(self, path, mode="r", encoding="utf-8", new_line="\n", external=True): - return open(path, mode, encoding=encoding, newline=new_line) - - def delete(self, path): - os.remove(path) - - -class ManagerFolder(BaseManager): - pass From 653fdfcedcc612e6d25bf4398949ebdd07ee1930 Mon Sep 17 00:00:00 2001 From: SnayperTihCreator Date: Fri, 20 Dec 2024 19:35:32 +0300 Subject: [PATCH 15/15] Update utilfile.py --- android/src/toga_android/libs/utilfile.py | 83 +++++++++++------------ 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/android/src/toga_android/libs/utilfile.py b/android/src/toga_android/libs/utilfile.py index 564891fb3f..156044fcb8 100644 --- a/android/src/toga_android/libs/utilfile.py +++ b/android/src/toga_android/libs/utilfile.py @@ -2,6 +2,7 @@ import asyncio as aio import io import os +from dataclasses import dataclass from java.io import BufferedReader, InputStreamReader, OutputStreamWriter from java.util import Objects @@ -11,7 +12,7 @@ class BaseFile(io.TextIOBase): def __init__(self, stream, binary, encoding, newLineInt): - self.aloop: aio.AbstractEventLoop = toga.App.app._impl + self.aloop: aio.AbstractEventLoop = toga.App.app._impl.loop self._stream = stream self._buffer = None self._is_binary = binary @@ -35,9 +36,15 @@ def close(self): self._buffer.close() +@dataclass +class BaseDataOpen: + binary: bool + encoding: str + newLineInt: int + + class BasePath(os.PathLike): modes = [] - file_impl = None def __init__(self, content, uri): self._uri = uri @@ -54,7 +61,7 @@ def open(self, mode, encoding="utf-8", new_line="\n"): It is allowed to use the following modes: R or RB""" ) _is_binary = "b" in mode - return self.file_impl(self._stream, _is_binary, encoding, _new_line_int) + return BaseDataOpen(_is_binary, encoding, _new_line_int) class BaseFileReader(BaseFile, abc.ABC): @@ -73,11 +80,11 @@ def readline(self, size=0): pass @abc.abstractmethod - def aread(self, size=0): + async def aread(self, size=0): pass @abc.abstractmethod - def areadline(self, size=0): + async def areadline(self, size=0): pass @@ -129,41 +136,21 @@ def read(self, size: int = 0) -> bytes | str: return res.decode(self._encoding) async def aread(self, size=0): - """A function for reading a string - :param size: the number of characters to be counted. + """Asynchronous function for reading a string + :parameter size: the number of characters to count. If it is equal to 0, the entire file will be read - :return: Data type str[bytes] + :returns: data type str[bytes] (depends on whether the 'b' flag was passed)""" self.check_open() - res = bytearray() - counter = size if size else "-1" - while counter: - resp: int = await self.aloop.run_in_executor(None, self._buffer.read) - if resp == -1: - break - res.append(resp) - if isinstance(counter, int): - counter -= 1 - if self._is_binary: - return bytes(res) - return res.decode(self._encoding) + return await self.aloop.run_in_executor(None, self.read, size) - def areadline(self, size=0): - return NotImplemented - - -class PathReader(BasePath): - - modes = ["r", "rb"] - file_impl = FileReader - - def open(self, mode="r", encoding="utf-8", new_line="\n"): - """A method for opening a file for reading along the path""" - self._stream = self._content.openInputStream(self._uri) - return super().open(mode.lower(), encoding, new_line) - - def __fspath__(self): - return str(self._uri) + async def areadline(self, size=0): + """Asynchronous function for reading lines from a file + : parameter size: the number of rows to count. + (Currently not implemented, added for future implementation) + :returns: data type str[byte] + (Depends on whether and flag was passed) or the str[byte] list""" + return await self.aloop.run_in_executor(None, self.readline, size) class BaseFileWriter(BaseFile, abc.ABC): @@ -184,7 +171,7 @@ def write(self, text: str | bytes): pass @abc.abstractmethod - def awrite(self, text): + async def awrite(self, text): pass @@ -197,10 +184,22 @@ def awrite(self, text): pass -class PathWriter(BasePath, abc.ABC): - modes = ["w", "wb"] - file_impl = FileWriter +class Path(BasePath): + modes = ["r", "rb", "w", "wb"] def open(self, mode="r", encoding="utf-8", new_line="\n"): - """A method for opening a file for writing along the path""" - return super().open(mode.lower(), encoding, new_line) + """A method for opening a file for reading along the path""" + dataOpen = super().open(mode.lower(), encoding, new_line) + if "r" in mode.lower(): + self._stream = self._content.openInputStream(self._uri) + return FileReader( + self._stream, dataOpen.binary, dataOpen.encoding, dataOpen.newLineInt + ) + if "w" in mode.lower(): + self._stream = self._content.openOutputStream(self._uri) + return FileWriter( + self._stream, dataOpen.binary, dataOpen.encoding, dataOpen.newLineInt + ) + + def __fspath__(self): + return str(self._uri)