summaryrefslogtreecommitdiff
path: root/utils/trackertestutils/__main__.py
diff options
context:
space:
mode:
Diffstat (limited to 'utils/trackertestutils/__main__.py')
-rw-r--r--utils/trackertestutils/__main__.py461
1 files changed, 461 insertions, 0 deletions
diff --git a/utils/trackertestutils/__main__.py b/utils/trackertestutils/__main__.py
new file mode 100644
index 000000000..859a82545
--- /dev/null
+++ b/utils/trackertestutils/__main__.py
@@ -0,0 +1,461 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2012-2013 Martyn Russell <martyn@lanedo.com>
+# Copyright (C) 2012 Sam Thursfield <sam.thursfield@codethink.co.uk>
+# Copyright (C) 2016,2019 Sam Thursfield <sam@afuera.me.uk>
+#
+# This is a tool for running development versions of Tracker.
+#
+# See README.md for usage information.
+#
+# 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.
+#
+# 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 argparse
+import collections
+import configparser
+import locale
+import logging
+import os
+import shlex
+import shutil
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+
+from gi.repository import Gio
+from gi.repository import GLib
+
+from . import dbusdaemon
+from . import helpers
+from . import mainloop
+
+# Script
+script_name = 'tracker-sandbox'
+script_about = "Tracker Sandbox developer tool."
+
+default_index_location = '/tmp/tracker-sandbox'
+
+store_pid = -1
+store_proc = None
+
+original_xdg_data_home = GLib.get_user_data_dir()
+
+# Template config file
+config_template = """
+[General]
+verbosity=0
+sched-idle=0
+initial-sleep=0
+
+[Monitors]
+enable-monitors=false
+
+[Indexing]
+throttle=0
+index-on-battery=true
+index-on-battery-first-time=true
+index-removable-media=false
+index-optical-discs=false
+low-disk-space-limit=-1
+index-recursive-directories=;
+index-single-directories=;
+ignored-directories=;
+ignored-directories-with-content=;
+ignored-files=
+crawling-interval=-1
+removable-days-threshold=3
+
+[Writeback]
+enable-writeback=false
+"""
+
+
+log = logging.getLogger('sandbox')
+
+
+# Environment / Clean up
+
+def environment_unset(dbus):
+ log.debug('Cleaning up processes ...')
+
+ dbus.stop()
+
+ # FIXME: clean up tracker-store, can't use 'tracker daemon ...' for this,
+ # that kills everything it finds in /proc sadly.
+ if store_pid > 0:
+ log.debug(' Killing Tracker store')
+ os.kill(store_pid, signal.SIGTERM)
+
+
+def environment_set_and_add_path(env, var, prefix, suffix):
+ new = os.path.join(prefix, suffix)
+
+ if var in env:
+ existing = env[var]
+ full = '%s:%s' % (new, existing)
+ else:
+ full = new
+
+ env[var] = full
+
+
+def create_sandbox(index_location, prefix=None, verbosity=0, dbus_config=None):
+ assert prefix is None or dbus_config is None
+
+ extra_env = {}
+
+ # Data
+ extra_env['XDG_DATA_HOME'] = '%s/data/' % index_location
+ extra_env['XDG_CONFIG_HOME'] = '%s/config/' % index_location
+ extra_env['XDG_CACHE_HOME'] = '%s/cache/' % index_location
+ extra_env['XDG_RUNTIME_DIR'] = '%s/run/' % index_location
+
+ # Prefix - only set if non-standard
+ if prefix and prefix != '/usr':
+ environment_set_and_add_path(extra_env, 'PATH', prefix, 'bin')
+ environment_set_and_add_path(extra_env, 'LD_LIBRARY_PATH', prefix, 'lib')
+ environment_set_and_add_path(extra_env, 'XDG_DATA_DIRS', prefix, 'share')
+
+ # Preferences
+ extra_env['TRACKER_USE_CONFIG_FILES'] = 'yes'
+
+ extra_env['G_MESSAGES_PREFIXED'] = 'all'
+
+ extra_env['TRACKER_VERBOSITY'] = str(verbosity)
+
+ log.debug('Using prefix location "%s"' % prefix)
+ log.debug('Using index location "%s"' % index_location)
+
+ sandbox = helpers.TrackerDBusSandbox(dbus_config, extra_env=extra_env)
+ sandbox.start()
+
+ # Update our own environment, so when we launch a subprocess it has the
+ # same settings as the Tracker daemons.
+ os.environ.update(extra_env)
+ os.environ['DBUS_SESSION_BUS_ADDRESS'] = sandbox.daemon.get_address()
+ os.environ['TRACKER_SANDBOX'] = '1'
+
+ return sandbox
+
+
+def config_set(content_locations_recursive=None, content_locations_single=None):
+ # Make sure File System miner is configured correctly
+ config_dir = os.path.join(os.environ['XDG_CONFIG_HOME'], 'tracker')
+ config_filename = os.path.join(config_dir, 'tracker-miner-fs.cfg')
+
+ log.debug('Using config file "%s"' % config_filename)
+
+ # Only update config if we're updating the database
+ os.makedirs(config_dir, exist_ok=True)
+
+ if not os.path.exists(config_filename):
+ f = open(config_filename, 'w')
+ f.write(config_template)
+ f.close()
+
+ log.debug(' Miner config file written')
+
+ # Set content path
+ config = configparser.ConfigParser()
+ config.optionxform = str
+ config.read(config_filename)
+
+ if content_locations_recursive:
+ log.debug("Using content locations: %s" %
+ content_locations_recursive)
+ if content_locations_single:
+ log.debug("Using non-recursive content locations: %s" %
+ content_locations_single)
+
+ def locations_gsetting(locations):
+ locations = [dir if dir.startswith('&') else os.path.abspath(dir)
+ for dir in locations]
+ return GLib.Variant('as', locations).print_(False)
+
+ if not config.has_section('General'):
+ config.add_section('General')
+
+ config.set('General', 'index-recursive-directories',
+ locations_gsetting(content_locations_recursive or []))
+ config.set('General', 'index-single-directories',
+ locations_gsetting(content_locations_single or []))
+
+ with open(config_filename, 'w') as f:
+ config.write(f)
+
+
+def link_to_mime_data():
+ '''Create symlink to $XDG_DATA_HOME/mime in our custom data home dir.
+
+ Mimetype detection seems to break horribly if the $XDG_DATA_HOME/mime
+ directory is missing. Since we have to override the normal XDG_DATA_HOME
+ path, we need to work around this by linking back to the real mime data.
+
+ '''
+ new_xdg_data_home = os.environ['XDG_DATA_HOME']
+ old_mime_dir = os.path.join(original_xdg_data_home, 'mime')
+ if os.path.exists(old_mime_dir):
+ new_mime_dir = os.path.join(new_xdg_data_home, 'mime')
+ if (not os.path.exists(new_mime_dir)
+ and not os.path.islink(new_mime_dir)):
+ os.makedirs(new_xdg_data_home, exist_ok=True)
+ os.symlink(
+ os.path.join(original_xdg_data_home, 'mime'), new_mime_dir)
+
+
+def argument_parser():
+ class expand_path(argparse.Action):
+ """Expand user- and relative-paths in filenames."""
+ # From https://gist.github.com/brantfaircloth/1443543
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, os.path.abspath(os.path.expanduser(values)))
+
+ parser = argparse.ArgumentParser(description=script_about)
+ parser.add_argument('--dbus-config', metavar='FILE', action=expand_path,
+ help="use a custom D-Bus config file to locate the "
+ "Tracker daemons. This can be used to run Tracker "
+ "from a build tree of tracker-miners.git, by "
+ "using the generated file ./tests/test-bus.conf")
+ parser.add_argument('-p', '--prefix', metavar='DIR', action=expand_path,
+ help="run Tracker from the given install prefix. You "
+ "can run the system version of Tracker by "
+ "specifying --prefix=/usr")
+ parser.add_argument('-v', '--verbosity', default=None,
+ choices=['0', '1', '2', '3', 'errors', 'minimal', 'detailed', 'debug'],
+ help="show debugging info from Tracker processes")
+ parser.add_argument('-i', '--index', metavar='DIR', action=expand_path,
+ default=default_index_location, dest='index_location',
+ help=f"directory to the index (default={default_index_location})")
+ parser.add_argument('--index-tmpdir', action='store_true',
+ help="create index in a temporary directory and "
+ "delete it on exit (useful for automated testing)")
+ parser.add_argument('--wait-for-miner', type=str, action='append',
+ help="wait for one or more daemons to start, and "
+ "return to idle for at least 1 second, before "
+ "exiting. Usually used with `tracker index` where "
+ "you should pass --wait-for-miner=Files and "
+ "--wait-for-miner=Extract")
+ parser.add_argument('--debug-dbus', action='store_true',
+ help="show stdout and stderr messages from every daemon "
+ "running on the sandbox session bus. By default we "
+ "only show messages logged by Tracker daemons.")
+ parser.add_argument('--debug-sandbox', action='store_true',
+ help="show debugging info from tracker-sandbox")
+ parser.add_argument('command', type=str, nargs='*', help="Command to run inside the shell")
+
+ return parser
+
+
+def verbosity_as_int(verbosity):
+ verbosity_map = {
+ 'errors': 0,
+ 'minimal': 1,
+ 'detailed': 2,
+ 'debug': 3
+ }
+ return verbosity_map.get(verbosity, int(verbosity))
+
+
+def init_logging(debug_sandbox, debug_dbus):
+ SANDBOX_FORMAT = "%(name)s: %(message)s"
+ DBUS_FORMAT = "%(message)s"
+
+ if debug_sandbox:
+ sandbox_log_handler = logging.StreamHandler()
+ sandbox_log_handler.setFormatter(logging.Formatter(SANDBOX_FORMAT))
+
+ root = logging.getLogger()
+ root.setLevel(logging.DEBUG)
+ root.addHandler(sandbox_log_handler)
+ else:
+ dbus_stderr = logging.getLogger('trackertestutils.dbusdaemon.stderr')
+ dbus_stdout = logging.getLogger('trackertestutils.dbusdaemon.stdout')
+
+ dbus_handler = logging.StreamHandler(stream=sys.stderr)
+ dbus_handler.setFormatter(logging.Formatter(DBUS_FORMAT))
+
+ if debug_dbus:
+ dbus_stderr.setLevel(logging.DEBUG)
+ dbus_stdout.setLevel(logging.DEBUG)
+ else:
+ dbus_stderr.setLevel(logging.INFO)
+ dbus_stdout.setLevel(logging.INFO)
+
+ dbus_stderr.addHandler(dbus_handler)
+ dbus_stdout.addHandler(dbus_handler)
+
+
+class MinerStatusWatch():
+ """This class provides a way to block waiting for miners to finish.
+
+ This is needed because of a deficiency in `tracker index`, see:
+ https://gitlab.gnome.org/GNOME/tracker/issues/122
+
+ """
+ def __init__(self, sandbox, miner_name):
+ self.dbus_name = 'org.freedesktop.Tracker1.Miner.' + miner_name
+ self.object_path = '/org/freedesktop/Tracker1/Miner/' + miner_name
+
+ self._sandbox = sandbox
+
+ # Stores a list of (time, status) pairs. This is used to determine
+ # if the miner has been idle continuously over a time peroid.
+ self._status_log = collections.deque()
+
+ def _log_status(self, time, status):
+ self._status_log.append((time, status))
+ if len(self._status_log) > 100:
+ self._status_log.popleft()
+
+ def setup(self):
+ log.debug(f"Set up status watch on {self.dbus_name}")
+ self._proxy = Gio.DBusProxy.new_sync(
+ self._sandbox.get_connection(),
+ Gio.DBusProxyFlags.NONE, None,
+ self.dbus_name, self.object_path, 'org.freedesktop.Tracker1.Miner',
+ None)
+
+ # FIXME: this doesn't appear to work, so we have to use polling.
+ #proxy.connect('g-signal', miner_signal_cb)
+
+ # This call will raise GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown
+ # if the miner name is invalid.
+ status = self._proxy.GetStatus()
+ self._log_status(time.time(), status)
+ log.debug(f"{self.dbus_name}: Current status: {status}")
+
+ def check_was_idle_for_time_period(self, period_seconds):
+ now = time.time()
+
+ status = self._proxy.GetStatus()
+ self._log_status(now, status)
+ log.debug(f"{self.dbus_name}: Current status: {status}")
+
+ cursor = len(self._status_log) - 1
+ previous_delta_from_now = 0
+ while True:
+ if cursor < 0 or self._status_log[cursor][1] != 'Idle':
+ if previous_delta_from_now >= period_seconds:
+ return True
+ else:
+ return False
+ previous_delta_from_now = (now - self._status_log[cursor][0])
+ cursor -= 1
+
+
+def wait_for_miners(watches):
+ # We wait 1 second after "Idle" status is seen before exiting, because the
+ # extractor goes to/from Idle frequently.
+ wait_for_idle_time = 1
+ while True:
+ status = [watch.check_was_idle_for_time_period(wait_for_idle_time) for watch in watches.values()]
+ if all(status):
+ break
+ else:
+ log.debug(f"Waiting for idle.")
+ time.sleep(0.1)
+
+
+def main():
+ locale.setlocale(locale.LC_ALL, '')
+
+ parser = argument_parser()
+ args = parser.parse_args()
+
+ init_logging(args.debug_sandbox, args.debug_dbus)
+
+ shell = os.environ.get('SHELL', '/bin/bash')
+
+ if args.prefix is None and args.dbus_config is None:
+ parser.print_help()
+ print("\nYou must specify either --dbus-config (to run Tracker from "
+ "a build tree) or --prefix (to run an installed Tracker).")
+ sys.exit(1)
+
+ if args.prefix is not None and args.dbus_config is not None:
+ raise RuntimeError(
+ "You cannot specify --dbus-config and --prefix at the same time. "
+ "Note that running Tracker from the build tree implies "
+ "--dbus-config.")
+
+ if args.verbosity is None:
+ verbosity = verbosity_as_int(os.environ.get('TRACKER_VERBOSITY', 0))
+ else:
+ verbosity = verbosity_as_int(args.verbosity)
+ if 'TRACKER_VERBOSITY' in os.environ:
+ if verbosity != int(os.environ['TRACKER_VERBOSITY']):
+ raise RuntimeError("Incompatible values for TRACKER_VERBOSITY "
+ "from environment and from --verbosity "
+ "parameter.")
+
+ if args.command is None and args.wait_for_miner is not None:
+ raise RuntimeError("--wait-for-miner cannot be used when opening an "
+ "interactive shell.")
+
+ index_location = None
+ index_tmpdir = None
+
+ if args.index_location != default_index_location and args.index_tmpdir:
+ raise RuntimeError("The --index-tmpdir flag is enabled, but --index= was also passed.")
+ if args.index_tmpdir:
+ index_location = index_tmpdir = tempfile.mkdtemp(prefix='tracker-sandbox')
+ else:
+ index_location = args.index_location
+
+ # Set up environment variables and foo needed to get started.
+ sandbox = create_sandbox(index_location, args.prefix, verbosity, dbus_config=args.dbus_config)
+ config_set()
+
+ link_to_mime_data()
+
+ miner_watches = {}
+ for miner in (args.wait_for_miner or []):
+ watch = MinerStatusWatch(sandbox, miner)
+ watch.setup()
+ miner_watches[miner] = watch
+
+ try:
+ if args.command:
+ command = [shell, '-c', ' '.join(shlex.quote(c) for c in args.command)]
+ log.debug("Running: %s", command)
+ subprocess.run(command)
+
+ if len(miner_watches) > 0:
+ wait_for_miners(miner_watches)
+ else:
+ if args.dbus_config:
+ print(f"Using Tracker daemons from build tree with D-Bus config {args.dbus_config}")
+ else:
+ print(f"Using Tracker daemons from prefix {args.prefix}")
+ print("Starting interactive Tracker sandbox shell... (type 'exit' to finish)")
+ print()
+
+ os.system(shell)
+ finally:
+ sandbox.stop()
+ if index_tmpdir:
+ shutil.rmtree(index_tmpdir, ignore_errors=True)
+
+
+# Entry point/start
+if __name__ == "__main__":
+ try:
+ main()
+ except RuntimeError as e:
+ sys.stderr.write(f"ERROR: {e}\n")
+ sys.exit(1)