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

Add --replace-all option for development #751

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
33 changes: 10 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,16 @@ ls --reverse -clt ~/.local/share/hamster*/*.db
```
Backup the last file in the list.

### Upgrading

### Kill hamster daemons
When installed from source, it is recommended to uninstall before
installing a new version. When using system packages or flatpak, you
should be able to just install the new version.

When trying a different version, make sure to kill the running daemons:
After upgrading, the hamster background services might still be running
the old version. To replace them, you can either log out, or run:

```bash
# either step-by-step, totally safe
pkill -f hamster-service
pkill -f hamster-windows-service
# check (should be empty)
pgrep -af hamster

# or be bold and kill them all at once:
pkill -ef hamster
```
hamster --replace-all

### Install from packages

Expand Down Expand Up @@ -206,22 +201,14 @@ flatpak uninstall org.gnome.Hamster
#### Development

During development (As explained above, backup `hamster.db` first !),
if only python files are changed
if only python files are changed
(*deeper changes such as the migration to gsettings require a new install*)
the changes can be quickly tested by
```
# either
pgrep -af hamster
# and kill them one by one
# or be bold and kill all processes with "hamster" in their command line
pkill -ef hamster
python3 src/hamster-service.py &
python3 src/hamster-cli.py
./src/hamster-cli.py --replace-all
```
Advantage: running uninstalled is detected, and windows are *not* called via
D-Bus, so that all the traces are visible.

Note: You'll need recent version of hamster installed on your system (or
Note: You'll need recent version of hamster installed on your system (or
[this workaround](https://github.com/projecthamster/hamster/issues/552#issuecomment-585166000)).

#### Running tests
Expand Down
93 changes: 66 additions & 27 deletions src/hamster-cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@

import sys, os
import argparse
import time
import re
import pathlib
import subprocess

import gi
gi.require_version('Gdk', '3.0') # noqa: E402
Expand Down Expand Up @@ -148,8 +151,8 @@ def on_activate(self, data=None):
def on_activate_window(self, action=None, data=None):
self._open_window(action.get_name(), data)

def on_activate_quit(self, data=None):
self.on_activate_quit()
def on_activate_quit(self, action=None, data=None):
self.quit()

def on_startup(self, data=None):
logger.debug("startup")
Expand Down Expand Up @@ -451,8 +454,6 @@ def version(self):
""")

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

import signal
signal.signal(signal.SIGINT, signal.SIG_DFL) # gtk3 screws up ctrl+c
Expand All @@ -467,6 +468,10 @@ def version(self):
choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'),
default='WARNING',
help="Set the logging level (default: %(default)s)")
parser.add_argument("--replace", action='store_true',
help="Replace an existing GUI process (if any) instead of activating it")
parser.add_argument("--replace-all", action='store_true',
help="Replace all existing hamster processes (if any)")
parser.add_argument("action", nargs="?", default="overview")
parser.add_argument('action_args', nargs=argparse.REMAINDER, default=[])

Expand All @@ -477,6 +482,41 @@ def version(self):
# hamster_logger for the rest
hamster_logger.setLevel(args.log_level)

if args.replace_all:
if hamster.installed:
from hamster import defs # only available when running installed
d = pathlib.Path(defs.LIBEXEC_DIR)
cmds = [d / 'hamster-service', d / 'hamster-windows-service']
else:
d = pathlib.Path(__file__).parent
cmds = [d / 'hamster-service.py', d / 'hamster-windows-service.py']

for cmd in cmds:
subprocess.run((cmd, '--replace'))

app = Hamster()
logger.debug("app instantiated")
if args.replace or args.replace_all:
app.register()
if app.get_is_remote():
# This code is prone to race conditions (if processing the quit
# takes longer than the sleep below, or if another GUI is
# bus activated before the new app), but gio.Application
# does not offer any way to pass a pre-claimed name or dbus
# connection, and always passes DO_NOT_QUEUE when claiming
# the name, preventing properly handling this race
# condition. But it is only the GUI, so the user can always
# just manually quit any existing GUI.
logger.debug("sending quit")
app.activate_action("quit")
time.sleep(2)
app = Hamster()
logger.debug("app reinstantiated")
app.register()
if app.get_is_remote():
logger.error("Failed to replace existing GUI")
sys.exit(1)

if not hamster.installed:
logger.info("Running in devel mode")

Expand All @@ -488,30 +528,29 @@ def version(self):
else:
action = args.action

if action in ("about", "add", "edit", "overview", "preferences"):
if action == "add" and args.action_args:
assert not unknown_args, "unknown options: {}".format(unknown_args)
# directly add fact from arguments
id_ = hamster_client.start(*args.action_args)
assert id_ > 0, "failed to add fact"
sys.exit(0)
if action == "add" and args.action_args:
assert not unknown_args, "unknown options: {}".format(unknown_args)
# directly add fact from arguments
id_ = hamster_client.start(*args.action_args)
assert id_ > 0, "failed to add fact"
sys.exit(0)
elif action in ("about", "add", "edit", "overview", "preferences"):
app.register()
if action == "edit":
assert len(args.action_args) == 1, (
"edit requires exactly one argument, got {}"
.format(args.action_args))
id_ = int(args.action_args[0])
assert id_ > 0, "received non-positive id : {}".format(id_)
action_data = glib.Variant.new_int32(id_)
else:
app.register()
if action == "edit":
assert len(args.action_args) == 1, (
"edit requires exactly one argument, got {}"
.format(args.action_args))
id_ = int(args.action_args[0])
assert id_ > 0, "received non-positive id : {}".format(id_)
action_data = glib.Variant.new_int32(id_)
else:
action_data = None
app.activate_action(action, action_data)
run_args = [sys.argv[0]] + unknown_args
logger.debug("run {}".format(run_args))
status = app.run(run_args)
logger.debug("app exited")
sys.exit(status)
action_data = None
app.activate_action(action, action_data)
run_args = [sys.argv[0]] + unknown_args
logger.debug("run {}".format(run_args))
status = app.run(run_args)
logger.debug("app exited")
sys.exit(status)
elif hasattr(hamster_client, action):
getattr(hamster_client, action)(*args.action_args)
else:
Expand Down
38 changes: 24 additions & 14 deletions src/hamster-service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python3
# nicked off gwibber

import sys

import dbus
import dbus.service

Expand All @@ -9,14 +11,15 @@

import hamster
from hamster import logger as hamster_logger
from hamster.lib import i18n
from hamster.lib import i18n, stuff
i18n.setup_i18n() # noqa: E402

from hamster.storage import db
from hamster.lib import datetime as dt
from hamster.lib import default_logger
from hamster.lib.dbus import (
DBusMainLoop,
claim_bus_name,
fact_signature,
from_dbus_date,
from_dbus_fact,
Expand All @@ -33,20 +36,12 @@
DBusMainLoop(set_as_default=True)
loop = glib.MainLoop()

if "org.gnome.Hamster" in dbus.SessionBus().list_names():
print("Found hamster-service already running, exiting")
quit()


class Storage(db.Storage, dbus.service.Object):
__dbus_object_path__ = "/org/gnome/Hamster"

def __init__(self, loop):
self.bus = dbus.SessionBus()
bus_name = dbus.service.BusName("org.gnome.Hamster", bus=self.bus)


dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)
def __init__(self, loop, bus, name_obj):
self.bus = bus
dbus.service.Object.__init__(self, name_obj, self.__dbus_object_path__)
db.Storage.__init__(self, unsorted_localized="")

self.mainloop = loop
Expand Down Expand Up @@ -452,6 +447,8 @@ def Version(self):
choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'),
default='WARNING',
help="Set the logging level (default: %(default)s)")
parser.add_argument("--replace", action='store_true',
help="Replace an existing process (if any)")

args = parser.parse_args()

Expand All @@ -460,6 +457,19 @@ def Version(self):
# hamster_logger for the rest
hamster_logger.setLevel(args.log_level)

print("hamster-service up")
storage = Storage(loop)
quit_method = (Storage.__dbus_object_path__, 'org.gnome.Hamster', 'Quit')
(bus, name_obj) = claim_bus_name("org.gnome.Hamster", quit_method=quit_method, replace=args.replace)
if name_obj is None:
if args.replace:
logger.error("Failed to replace existing hamster-service (it did not quit within timeout), exiting")
else:
logger.error("Found hamster-service already running, exiting")
sys.exit(1)

storage = Storage(loop, bus, name_obj)
logger.info("hamster-service up")

# Daemonize once we're succesfully started up and registered on dbus
stuff.daemonize()

loop.run()
36 changes: 25 additions & 11 deletions src/hamster-windows-service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@
import dbus.service
import os.path
import subprocess
import sys

from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib as glib

import hamster
from hamster.lib import default_logger, stuff
from hamster.lib.dbus import claim_bus_name

logger = default_logger(__file__)

DBusGMainLoop(set_as_default=True)
loop = glib.MainLoop()

if "org.gnome.Hamster.WindowServer" in dbus.SessionBus().list_names():
print("Found hamster-window-service already running, exiting")
quit()


# Legacy server. Still used by the shell-extension.
# New code _could_ access the org.gnome.Hamster.GUI actions directly,
# although the exact action names/data are subject to change.
Expand All @@ -30,12 +29,11 @@
class WindowServer(dbus.service.Object):
__dbus_object_path__ = "/org/gnome/Hamster/WindowServer"

def __init__(self, loop):
def __init__(self, loop, bus, name_obj):
self.app = True
self.mainloop = loop
self.bus = dbus.SessionBus()
bus_name = dbus.service.BusName("org.gnome.Hamster.WindowServer", bus=self.bus)
dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)
self.bus = bus
dbus.service.Object.__init__(self, name_obj, self.__dbus_object_path__)

@dbus.service.method("org.gnome.Hamster")
def Quit(self):
Expand Down Expand Up @@ -83,8 +81,24 @@ def preferences(self):

glib.set_prgname(str(_("hamster-windows-service")))

window_server = WindowServer(loop)
import argparse
parser = argparse.ArgumentParser(description="Hamster time tracker D-Bus service")
parser.add_argument("--replace", action='store_true',
help="Replace an existing process (if any)")
args = parser.parse_args()

quit_method = (WindowServer.__dbus_object_path__, 'org.gnome.Hamster', 'Quit')
(bus, name_obj) = claim_bus_name("org.gnome.Hamster.WindowServer", quit_method=quit_method, replace=args.replace)
if name_obj is None:
if args.replace:
logger.error("Failed to replace existing hamster-windows-service (it did not quit within timeout), exiting")
else:
logger.error("Found hamster-windows-service already running, exiting")
sys.exit(1)
window_server = WindowServer(loop, bus, name_obj)
logger.info("hamster-window-service up")

print("hamster-window-service up")
# Daemonize once we're succesfully started up and registered on dbus
stuff.daemonize()

loop.run()
13 changes: 4 additions & 9 deletions src/hamster/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,10 @@ def conn(self):
server: {}
client: {}

This is sometimes used during bisections,
but generally calls for trouble.

Remember to kill hamster daemons after any version change
(this is safe):
pkill -f hamster-service
pkill -f hamster-windows-service
see also:
https://github.com/projecthamster/hamster#kill-hamster-daemons
To replace the running services, you can use:

hamster --replace-all

""".format(server_version, client_version)
)
)
Expand Down
1 change: 1 addition & 0 deletions src/hamster/defs.py.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
DATA_DIR = "@DATADIR@"
LIBEXEC_DIR = "@LIBEXECDIR@"
VERSION = "@VERSION@"
Loading
Loading