Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
# Install a specific python version as configured
PACKAGES="$PACKAGES python${{matrix.python-version}}"
# Normal dependencies
PACKAGES="$PACKAGES gettext intltool python3-gi python3-cairo python3-gi-cairo python3-distutils python3-dbus python3-xdg libglib2.0-dev libglib2.0-bin gir1.2-gtk-3.0 gtk-update-icon-cache"
PACKAGES="$PACKAGES gettext intltool python3-gi python3-cairo python3-gi-cairo python3-dbus python3-xdg libglib2.0-dev libglib2.0-bin gir1.2-gtk-3.0 gtk-update-icon-cache"
# For dbus-launch
PACKAGES="$PACKAGES dbus-x11"

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ commands). Older versions are not supported.
###### Ubuntu (tested in 19.04 and 18.04)

```bash
sudo apt install gettext intltool python3-gi python3-cairo python3-gi-cairo python3-distutils python3-dbus libglib2.0-dev libglib2.0-bin gir1.2-gtk-3.0 gtk-update-icon-cache
sudo apt install gettext intltool python3-gi python3-cairo python3-gi-cairo python3-dbus libglib2.0-dev libglib2.0-bin gir1.2-gtk-3.0 gtk-update-icon-cache
# and for documentation
sudo apt install itstool yelp
```
Expand Down
97 changes: 86 additions & 11 deletions src/hamster-cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import sys, os
import argparse
import re
import dbus

import gi
gi.require_version('Gdk', '3.0') # noqa: E402
Expand All @@ -37,12 +38,8 @@

import hamster

from hamster import client, reports
from hamster import client
from hamster import logger as hamster_logger
from hamster.about import About
from hamster.edit_activity import CustomFactController
from hamster.overview import Overview
from hamster.preferences import PreferencesEditor
from hamster.lib import default_logger, stuff
from hamster.lib import datetime as dt
from hamster.lib.fact import Fact
Expand All @@ -51,6 +48,39 @@
logger = default_logger(__file__)


def _should_fallback_to_local_storage(error):
"""Whether a D-Bus connection error should trigger local storage fallback."""
if not isinstance(error, dbus.DBusException):
return False

dbus_name = (error.get_dbus_name() or "").lower()
message = str(error).lower()

known_names = {
"org.freedesktop.dbus.error.noserver",
"org.freedesktop.dbus.error.filenotfound",
"org.freedesktop.dbus.error.spawn.execfailed",
}
if dbus_name in known_names:
return True

known_fragments = (
"unable to autolaunch a dbus-daemon",
"failed to connect to socket",
"cannot autolaunch d-bus",
"failed to open connection to session message bus",
)
return any(fragment in message for fragment in known_fragments)


def _get_server_version(storage):
connection = dbus.Interface(
storage.bus.get_object('org.gnome.Hamster', '/org/gnome/Hamster'),
dbus_interface='org.gnome.Hamster'
)
return str(connection.Version())


def word_wrap(line, max_len):
"""primitive word wrapper"""
lines = []
Expand Down Expand Up @@ -163,6 +193,7 @@ def _open_window(self, name, data=None):

if name == "about":
if not self.about_controller:
from hamster.about import About
# silence warning "GtkDialog mapped without a transient parent"
# https://stackoverflow.com/a/38408127/3565696
_dummy = gtk.Window()
Expand All @@ -175,17 +206,20 @@ def _open_window(self, name, data=None):
# Or should we just discard the forgotten one ?
logger.warning("Fact controller already active. Please close first.")
else:
from hamster.edit_activity import CustomFactController
fact_id = data.get_int32() if data else None
self.fact_controller = CustomFactController(name, fact_id=fact_id)
logger.debug("new CustomFactController")
controller = self.fact_controller
elif name == "overview":
if not self.overview_controller:
from hamster.overview import Overview
self.overview_controller = Overview()
logger.debug("new Overview")
controller = self.overview_controller
elif name == "preferences":
if not self.preferences_controller:
from hamster.preferences import PreferencesEditor
self.preferences_controller = PreferencesEditor()
logger.debug("new PreferencesEditor")
controller = self.preferences_controller
Expand Down Expand Up @@ -228,7 +262,44 @@ class HamsterCli(object):
"""Command line interface."""

def __init__(self):
self.storage = client.Storage()
self.storage = None
force_dbus = os.getenv("HAMSTER_CLI_FORCE_DBUS", "").lower() in ("1", "true", "yes")

try:
self.storage = client.Storage()
except dbus.DBusException as error:
if not _should_fallback_to_local_storage(error):
raise

from hamster.storage import db
self.storage = db.Storage(unsorted_localized="")
logger.warning(
"Session D-Bus unavailable (%s). Falling back to direct local database access.",
error,
)

if isinstance(self.storage, client.Storage) and not hamster.installed and not force_dbus:
try:
server_version = _get_server_version(self.storage)
if server_version != hamster.__version__:
from hamster.storage import db
logger.warning(
"Detected daemon version mismatch (server=%s, client=%s). "
"Using local database access in development mode.",
server_version,
hamster.__version__,
)
self.storage = db.Storage(unsorted_localized="")
except dbus.DBusException as error:
if not _should_fallback_to_local_storage(error):
raise
from hamster.storage import db
self.storage = db.Storage(unsorted_localized="")
logger.warning(
"Session D-Bus unavailable during daemon probe (%s). "
"Falling back to direct local database access.",
error,
)


def assist(self, *args):
Expand Down Expand Up @@ -267,6 +338,8 @@ def stop(self, *args):


def export(self, *args):
from hamster import reports

args = args or []
export_format, start_time, end_time = "html", None, None
if args:
Expand Down Expand Up @@ -450,10 +523,6 @@ def version(self):
August 2012. Will check against activity, category, description and tags
""")

hamster_client = HamsterCli()
app = Hamster()
logger.debug("app instanciated")

import signal
signal.signal(signal.SIGINT, signal.SIG_DFL) # gtk3 screws up ctrl+c

Expand Down Expand Up @@ -489,7 +558,10 @@ def version(self):
action = args.action

if action in ("about", "add", "edit", "overview", "preferences"):
app = Hamster()
logger.debug("app instanciated")
if action == "add" and args.action_args:
hamster_client = HamsterCli()
assert not unknown_args, "unknown options: {}".format(unknown_args)
# directly add fact from arguments
id_ = hamster_client.start(*args.action_args)
Expand All @@ -512,7 +584,10 @@ def version(self):
status = app.run(run_args)
logger.debug("app exited")
sys.exit(status)
elif hasattr(hamster_client, action):
else:
hamster_client = HamsterCli()

if hasattr(hamster_client, action):
getattr(hamster_client, action)(*args.action_args)
else:
sys.exit(usage % {'prog': sys.argv[0]})
7 changes: 6 additions & 1 deletion src/hamster/lib/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
logger = logging.getLogger(__name__) # noqa: E402

import os
import dbus
from hamster.client import Storage

from gi.repository import Gdk as gdk
Expand Down Expand Up @@ -120,7 +121,11 @@ def __init__(self):
self.data_dir = os.path.join(module_dir, '..', '..', '..', 'data')

self.data_dir = os.path.realpath(self.data_dir)
self.storage = Storage()
try:
self.storage = Storage()
except dbus.DBusException as error:
logger.warning("Session D-Bus unavailable, runtime storage disabled: %s", error)
self.storage = None
self.home_data_dir = os.path.realpath(os.path.join(glib.get_user_data_dir(), "hamster"))


Expand Down
Loading