Skip to content

Commit

Permalink
Merge pull request #1964 from freakboy3742/app-paths
Browse files Browse the repository at this point in the history
Rework implementation of app paths to factor out common implementations.
  • Loading branch information
mhsmith committed Jun 8, 2023
2 parents 86ee779 + aed519a commit 645140a
Show file tree
Hide file tree
Showing 67 changed files with 841 additions and 1,242 deletions.
4 changes: 2 additions & 2 deletions android/src/toga_android/factory.py
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
@@ -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
@@ -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
@@ -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
@@ -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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
@@ -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()

0 comments on commit 645140a

Please sign in to comment.