Skip to content

Commit

Permalink
test: User callback log output
Browse files Browse the repository at this point in the history
Related to #1648 and #1269
  • Loading branch information
buhtz authored Mar 9, 2024
1 parent ed282ad commit c1a50a9
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 54 deletions.
22 changes: 16 additions & 6 deletions common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ class Config(configfile.ConfigFileWithProfiles):
PLUGIN_MANAGER = pluginmanager.PluginManager()

def __init__(self, config_path=None, data_path=None):
"""Back In Time configuration (and much more then this).
Args:
config_path (str): Full path to the config file
(default: `~/.config/backintime/config`).
data_path (str): It is $XDG_DATA_HOME (default: `~/.local/share`).
"""
# Note: The main profiles name here is translated using the systems
# current locale because the language code in the config file wasn't
# read yet.
Expand Down Expand Up @@ -174,13 +181,16 @@ def __init__(self, config_path=None, data_path=None):
else:
self._LOCAL_CONFIG_PATH = os.path.abspath(config_path)
self._LOCAL_CONFIG_FOLDER = os.path.dirname(self._LOCAL_CONFIG_PATH)
old_path = os.path.join(self._LOCAL_CONFIG_FOLDER, 'config2')

if os.path.exists(old_path):
if os.path.exists(self._LOCAL_CONFIG_PATH):
os.remove(old_path)
else:
os.rename(old_path, self._LOCAL_CONFIG_PATH)
# (buhtz) Introduced in 2009 via commit 5b26575be4.
# Ready to remove after 15 years.
# old_path = os.path.join(self._LOCAL_CONFIG_FOLDER, 'config2')

# if os.path.exists(old_path):
# if os.path.exists(self._LOCAL_CONFIG_PATH):
# os.remove(old_path)
# else:
# os.rename(old_path, self._LOCAL_CONFIG_PATH)

# Load global config file
self.load(self._GLOBAL_CONFIG_PATH)
Expand Down
5 changes: 5 additions & 0 deletions common/pluginmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,20 @@ def load(self, snapshots = None, cfg = None, force = False):
self.hasGuiPlugins = False

loadedPlugins = []

# TODO 09/28/2022: Move hard coded plugin folders to configuration
for path in ('plugins', 'common/plugins', 'qt/plugins'):
fullPath = tools.backintimePath(path)

if os.path.isdir(fullPath):
logger.debug('Register plugin path %s' %fullPath, self)
tools.registerBackintimePath(path)

for f in os.listdir(fullPath):

if f not in loadedPlugins and f.endswith('.py') and not f.startswith('__'):
logger.debug('Probing plugin %s' % f, self)

try:
module = __import__(f[: -3])
module_dict = module.__dict__
Expand Down
83 changes: 48 additions & 35 deletions common/plugins/usercallbackplugin.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Back In Time
# Copyright (C) 2008-2022 Oprea Dan, Bart de Koning, Richard Bailey, Germar Reitze
# Back In Time
# Copyright (C) 2008-2022 Oprea Dan, Bart de Koning, Richard Bailey,
# Germar Reitze
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import os
import pluginmanager
Expand All @@ -23,7 +23,7 @@
from subprocess import Popen, PIPE
from exceptions import StopException

_=gettext.gettext
_ = gettext.gettext


class UserCallbackPlugin(pluginmanager.Plugin):
Expand All @@ -33,14 +33,11 @@ class UserCallbackPlugin(pluginmanager.Plugin):
files) about different steps ("events") in the backup process
via the :py:class:`pluginmanager.PluginManager`.
This plugin calls a user-defined script file ("user-callback")
that is located in this folder:
$XDG_CONFIG_HOME/backintime/user-callback
(by default $XDG_CONFIG_HOME is ~/.config)
This plugin calls a user-defined script file ("user-callback"). By default
that file is located in the config folder `$XDG_CONFIG_HOME/backintime/`
or another folder if command line optioni `--config` is used.
The user-callback script is called with up to five
positional arguments:
The user-callback script is called with up to five positional arguments:
1. The profile ID from the config file (1=Main Profile, ...)
2. The profile name (from the config file)
Expand All @@ -67,37 +64,53 @@ def __init__(self):
def init(self, snapshots):
self.config = snapshots.config
self.script = self.config.takeSnapshotUserCallback()
if not os.path.exists(self.script):
return False
return True

# TODO 09/28/2022: This method should be private (__callback)
return os.path.exists(self.script)

# TODO 09/28/2022: This method should be private (_callback)
def callback(self, *args, profileID = None):
if profileID is None:
profileID = self.config.currentProfile()

profileName = self.config.profileName(profileID)
cmd = [self.script, profileID, profileName]
cmd.extend([str(x) for x in args])
logger.debug('Call user-callback: %s' %' '.join(cmd), self)
cmd.extend(str(x) for x in args)

logger.debug(f'Call user-callback: {" ".join(cmd)}', self)

if self.config.userCallbackNoLogging():
stdout, stderr = None, None
else:
stdout, stderr = PIPE, PIPE

try:
callback = Popen(cmd,
stdout = stdout,
stderr = stderr,
universal_newlines = True)
stdout=stdout,
stderr=stderr,
universal_newlines=True)
output = callback.communicate()

# Stdout
if output[0]:
logger.info('user-callback returned \'%s\'' %output[0].strip('\n'), self)
logger.info("user-callback returned '"
+ output[0].strip('\n') + "'",
self)

# Stderr
if output[1]:
logger.error('user-callback returned \'%s\'' %output[1].strip('\n'), self)
logger.error("user-callback returned '"
+ output[1].strip('\n') + "'",
self)

if callback.returncode != 0:
logger.warning('user-callback returncode: %s' %callback.returncode, self)
logger.warning(
f'user-callback returncode: {callback.returncode}', self)
raise StopException()

except OSError as e:
logger.error("Exception when trying to run user callback: %s" % e.strerror, self)
logger.error(
f'Exception when trying to run user callback: {e.strerror}',
self)

def processBegin(self):
self.callback('1')
Expand Down
7 changes: 6 additions & 1 deletion common/snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ class Snapshots:
"""
Collection of take-snapshot and restore commands.
BUHTZ 2022-10-09: In my understanding this the representation of a
BUHTZ 2022-10-09: In my understanding this is the representation of a
snapshot in the "application layer". This seems to be the difference to
the class `SID` which represents a snapshot in the "data layer".
BUHTZ 2024-02-23: Not sure but it seems to be one concret snapshot and
not a collection of snapshots. In this case the class name is missleading
because it is in plural form.
Args:
cfg (config.Config): current config
"""
Expand Down Expand Up @@ -1370,6 +1374,7 @@ def takeSnapshot(self, sid, now, include_folders):
# TODO
# Process return value with rsync exit code to recognize errors that
# cannot be recognized by parsing the rsync output currently

rsync_exit_code = proc.run()
# Fix for #1491 and #489
# Note that the return value (containing the exit code) of the
Expand Down
1 change: 1 addition & 0 deletions common/test/test_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def test_with_pylint(self):
'E1101', # no-member
'W1401', # anomalous-backslash-in-string (invalid escape sequence)
'E0401', # import-error
'I0021', # useless-suppression
]
cmd.append('--enable=' + ','.join(err_codes))

Expand Down
Loading

0 comments on commit c1a50a9

Please sign in to comment.