Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework implementation of app paths to factor out common implementations. #1964

Merged
merged 17 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android/src/toga_android/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .fonts import Font
from .icons import Icon
from .images import Image
from .paths import paths
from .paths import Paths
from .widgets.box import Box
from .widgets.button import Button
from .widgets.canvas import Canvas
Expand Down Expand Up @@ -59,6 +59,6 @@ def not_implemented(feature):
"Window",
"DetailedList",
"not_implemented",
"paths",
"Paths",
"dialogs",
]
3 changes: 2 additions & 1 deletion android/src/toga_android/fonts.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os

import toga
from toga.fonts import (
_REGISTERED_FONT_CACHE,
BOLD,
Expand Down Expand Up @@ -51,7 +52,7 @@ def apply(self, tv, default_size, default_typeface):
)
if font_key in _REGISTERED_FONT_CACHE:
font_path = str(
self.interface.factory.paths.app / _REGISTERED_FONT_CACHE[font_key]
toga.App.app.paths.app / _REGISTERED_FONT_CACHE[font_key]
)
if os.path.isfile(font_path):
typeface = Typeface.createFromFile(font_path)
Expand Down
36 changes: 9 additions & 27 deletions android/src/toga_android/paths.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,24 @@
import sys
from pathlib import Path

import toga
from toga import App


class Paths:
# Allow instantiating Path object via the factory
Path = Path
def __init__(self, interface):
self.interface = interface

@property
def __context(self):
return App.app._impl.native.getApplicationContext()

def __init__(self):
# On Android, __main__ only exists during app startup, so cache its location now.
self._app = Path(sys.modules["__main__"].__file__).parent
def get_config_path(self):
return Path(self.__context.getFilesDir().getPath()) / "config"

@property
def app(self):
return self._app

@property
def data(self):
return Path(self.__context.getFilesDir().getPath())
def get_data_path(self):
return Path(self.__context.getFilesDir().getPath()) / "data"

@property
def cache(self):
def get_cache_path(self):
return Path(self.__context.getCacheDir().getPath())

@property
def logs(self):
return self.data

@property
def toga(self):
"""Return a path to a Toga resources."""
return Path(toga.__file__).parent


paths = Paths()
def get_logs_path(self):
return Path(self.__context.getFilesDir().getPath()) / "log"
31 changes: 31 additions & 0 deletions android/tests_backend/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from pathlib import Path

from toga_android.libs.activity import MainActivity

from .probe import BaseProbe


class AppProbe(BaseProbe):
def __init__(self, app):
super().__init__()
self.app = app
assert isinstance(self.app._impl.native, MainActivity)

def get_app_context(self):
return self.app._impl.native.getApplicationContext()

@property
def config_path(self):
return Path(self.get_app_context().getFilesDir().getPath()) / "config"

@property
def data_path(self):
return Path(self.get_app_context().getFilesDir().getPath()) / "data"

@property
def cache_path(self):
return Path(self.get_app_context().getCacheDir().getPath())

@property
def logs_path(self):
return Path(self.get_app_context().getFilesDir().getPath()) / "log"
19 changes: 19 additions & 0 deletions android/tests_backend/probe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import asyncio

from toga.fonts import SYSTEM


class BaseProbe:
def assert_font_family(self, expected):
actual = self.font.family
if expected == SYSTEM:
assert actual == "sans-serif"
else:
assert actual == expected

async def redraw(self, message=None):
"""Request a redraw of the app, waiting until that redraw has completed."""
# If we're running slow, wait for a second
if self.app.run_slow:
print("Waiting for redraw" if message is None else message)
await asyncio.sleep(1)
18 changes: 5 additions & 13 deletions android/tests_backend/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from android.os import Build
from android.view import View, ViewTreeObserver
from toga.colors import TRANSPARENT
from toga.fonts import SYSTEM
from toga.style.pack import JUSTIFY, LEFT

from ..probe import BaseProbe
from .properties import toga_color


Expand All @@ -28,8 +28,10 @@ def onGlobalLayout(self):
self.event.clear()


class SimpleProbe:
class SimpleProbe(BaseProbe):
def __init__(self, widget):
super().__init__()
self.app = widget.app
self.widget = widget
self.native = widget._impl.native
self.layout_listener = LayoutListener()
Expand Down Expand Up @@ -70,22 +72,12 @@ def assert_alignment(self, expected):
else:
assert actual == expected

def assert_font_family(self, expected):
actual = self.font.family
if expected == SYSTEM:
assert actual == "sans-serif"
else:
assert actual == expected

async def redraw(self, message=None):
"""Request a redraw of the app, waiting until that redraw has completed."""
self.native.requestLayout()
await self.layout_listener.event.wait()

# If we're running slow, wait for a second
if self.widget.app.run_slow:
print("Waiting for redraw" if message is None else message)
await asyncio.sleep(1)
await super().redraw(message=message)

@property
def enabled(self):
Expand Down
1 change: 1 addition & 0 deletions changes/1964.feature.1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Paths property of apps now has 100% test coverage, and complete API documentation.
1 change: 1 addition & 0 deletions changes/1964.feature.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The app paths now include a ``config`` path for storing config files.
1 change: 1 addition & 0 deletions changes/1964.removal.1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The location returned by ``toga.App.paths.app`` is now the folder that contains the Python source file that defines the app class used by the app. If you are using a ``toga.App`` instance directly, this may alter the path that is returned.
1 change: 1 addition & 0 deletions changes/1964.removal.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
On Winforms, if an application doesn't define an author, an author of ``Unknown`` is now used in application data paths, rather than ``Toga``.
1 change: 1 addition & 0 deletions changes/1964.removal.3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Winforms now returns ``AppData/Local/<Author Name>/<App Name>/Data`` as the user data file location, rather than ``AppData/Local/<Author Name>/<App Name>``.
1 change: 1 addition & 0 deletions changes/1964.removal.4.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
On Android, the user data folder is now a ``data`` subdirectory of the location returned by ``context.getFilesDir()``, rather than the bare ``context.getFilesDir()`` location.
1 change: 1 addition & 0 deletions changes/1964.removal.5.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GTK now returns ``~/.local/state/appname/log`` as the log file location, rather than ``~/.cache/appname/log``.
4 changes: 2 additions & 2 deletions cocoa/src/toga_cocoa/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .fonts import Font
from .icons import Icon
from .images import Image
from .paths import paths
from .paths import Paths

# Widgets
from .widgets.activityindicator import ActivityIndicator
Expand Down Expand Up @@ -50,7 +50,7 @@ def not_implemented(feature):
"Font",
"Icon",
"Image",
"paths",
"Paths",
"dialogs",
# Widgets
"ActivityIndicator",
Expand Down
37 changes: 7 additions & 30 deletions cocoa/src/toga_cocoa/paths.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,20 @@
import sys
from pathlib import Path

import toga
from toga import App


class Paths:
# Allow instantiating Path object via the factory
Path = Path
def __init__(self, interface):
self.interface = interface

@property
def app(self):
try:
return Path(sys.modules["__main__"].__file__).parent
except KeyError:
# If we're running in test conditions,
# there is no __main__ module.
return Path.cwd()
except AttributeError:
# If we're running at an interactive prompt,
# the __main__ module isn't file-based.
return Path.cwd()
def get_config_path(self):
return Path.home() / "Library" / "Preferences" / App.app.app_id

@property
def data(self):
def get_data_path(self):
return Path.home() / "Library" / "Application Support" / App.app.app_id

@property
def cache(self):
def get_cache_path(self):
return Path.home() / "Library" / "Caches" / App.app.app_id

@property
def logs(self):
def get_logs_path(self):
return Path.home() / "Library" / "Logs" / App.app.app_id

@property
def toga(self):
"""Return a path to a Toga resources."""
return Path(toga.__file__).parent


paths = Paths()
Loading