Skip to content

Commit

Permalink
Follow XDG base directory specification
Browse files Browse the repository at this point in the history
To become a better citizen, we now follow the XDG base directory
specifications - this makes it easier to configure where we dump data,
and helps avoid cluttering users' home directories.

We still read ~/.webmacs, but we give a deprecation warning - we
intend to ignore this directory in the future.
  • Loading branch information
TLATER committed Feb 21, 2019
1 parent 034a46a commit f0c962e
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 24 deletions.
2 changes: 1 addition & 1 deletion docs/user_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ User configuration
==================

**webmacs** can be configured by writing Python code. The files should live in
a ``~/.webmacs/init`` directory, starting with an ``__init__.py`` file.
a ``$XDG_CONFIG_HOME/webmacs/init`` directory, starting with an ``__init__.py`` file.

If this file exists, it will be loaded early in the application.

Expand Down
6 changes: 4 additions & 2 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from webmacs import (windows, buffers, WINDOWS_HANDLER, current_buffer,
current_window, current_minibuffer)
from webmacs import variables as wvariables
from webmacs.xdg_utils import XDG_DATA_HOME, XDG_CACHE_HOME
from webmacs.webbuffer import create_buffer
from webmacs.window import Window
from webmacs.webbuffer import close_buffer
Expand Down Expand Up @@ -78,8 +79,9 @@ def qapp(wm, qapp_args):
_app_requires()
global _app
# TODO FIXME use another path for tests
conf_path = os.path.join(os.path.expanduser("~"), ".webmacs")
_app = Application(conf_path, ["webmacs"])
conf_path = os.path.join(XDG_DATA_HOME, "webmacs")
cache_path = os.path.join(XDG_CACHE_HOME, "webmacs")
_app = Application(conf_path, cache_path, ["webmacs"])
return _app


Expand Down
6 changes: 5 additions & 1 deletion webmacs/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _app_requires():
class Application(QApplication):
INSTANCE = None

def __init__(self, conf_path, args, instance_name="default"):
def __init__(self, conf_path, cache_path, args, instance_name="default"):
QApplication.__init__(self, args)
self.__class__.INSTANCE = self
self.instance_name = instance_name
Expand All @@ -138,6 +138,7 @@ def __init__(self, conf_path, args, instance_name="default"):
self._conf_path = conf_path
if not os.path.isdir(self.profiles_path()):
os.makedirs(self.profiles_path())
self._cache_path = cache_path

self._interceptor = UrlInterceptor(self)

Expand Down Expand Up @@ -170,6 +171,9 @@ def __init__(self, conf_path, args, instance_name="default"):

self.network_manager = QNetworkAccessManager(self)

def cache_path(self):
return self._cache_path

def conf_path(self):
return self._conf_path

Expand Down
5 changes: 3 additions & 2 deletions webmacs/ipc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
QDir
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
from . import version
from .xdg_utils import XDG_RUNTIME_DIR


HEADER_FMT = "!I"
Expand Down Expand Up @@ -76,8 +77,8 @@ class IpcServer(QObject):
@classmethod
def get_sock_name(cls, instance):
if instance == "default":
return "webmacs.ipc"
return "webmacs.{}.ipc".format(instance)
return os.path.join(XDG_RUNTIME_DIR, "webmacs.ipc")
return os.path.join(XDG_RUNTIME_DIR, "webmacs.{}.ipc".format(instance))

@classmethod
def list_all_instances(cls, check=True):
Expand Down
44 changes: 37 additions & 7 deletions webmacs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@

from .ipc import IpcServer
from . import variables, filter_webengine_output
from .xdg_utils import XDG_CACHE_HOME, XDG_CONFIG_HOME, XDG_DATA_HOME


log_to_disk = variables.define_variable(
"log-to-disk-max-files",
"Maximum number of log files to keep. Log files are stored in"
" ~/.webmacs/logs. Setting this to 0 will deactivate file logging"
" completely.",
" $XDG_DATA_HOME/webmacs/logs. Setting this to 0 will deactivate file "
" logging completely.",
0,
type=variables.Int(min=0),
)
Expand Down Expand Up @@ -130,6 +131,11 @@ def parse_args(argv=None):
" If the given instance name is the empty string, an"
" automatically generated name will be used.")

parser.add_argument("-c", "--config",
default=os.path.join(XDG_CONFIG_HOME, "webmacs"),
help="The path from which to load the configuration "
"module.")

parser.add_argument("--list-instances", action="store_true",
help="List running instances and exit.")

Expand Down Expand Up @@ -236,9 +242,33 @@ def main():
if n.isdigit()]
opts.instance = str(max(uniq) + 1) if uniq else "1"

conf_path = os.path.join(os.path.expanduser("~"), ".webmacs")
conf_path = opts.config
if not os.path.isdir(conf_path):
os.makedirs(conf_path)
deprecated = os.path.expanduser("~/.webmacs")
if os.path.isdir(deprecated):
# Since logging has not been setup yet, use manual print
print("Warning: '{}' as an init directory has been deprecated. "
"Please use '{}' or configure it using '-c' instead."
.format(deprecated, conf_path), file=sys.stderr)
conf_path = deprecated
else:
os.makedirs(conf_path)

data_path = os.path.join(XDG_DATA_HOME, "webmacs")
if not os.path.isdir(data_path):
deprecated = os.path.expanduser("~/.webmacs")
if os.path.isdir(deprecated):
# Since logging has not been setup yet, use manual print
print("Warning: '{}' as a data directory has been deprecated. "
"Please move your profile and adblock directories to '{}'."
.format(deprecated, data_path), file=sys.stderr)
data_path = deprecated
else:
os.makedirs(data_path)

cache_path = os.path.join(XDG_CACHE_HOME, "webmacs")
if not os.path.isdir(data_path):
os.makedirs(cache_path)

out_filter = filter_webengine_output.make_filter()

Expand Down Expand Up @@ -271,7 +301,7 @@ def main():
"Error reading the user configuration."
)

app = Application(conf_path, [
app = Application(data_path, cache_path, [
# The first argument passed to the QApplication args defines
# the x11 property WM_CLASS.
"webmacs" if opts.instance == "default"
Expand All @@ -290,13 +320,13 @@ def main():
user_init.init(opts)
except Exception:
_handle_user_init_error(
conf_path,
data_path,
"Error executing user init function in %s."
% user_init.__file__
)

if log_to_disk.value > 0:
setup_logging_on_disk(os.path.join(conf_path, "logs"),
setup_logging_on_disk(os.path.join(data_path, "logs"),
backup_count=log_to_disk.value)
app.post_init()
signal_wakeup(app)
Expand Down
25 changes: 14 additions & 11 deletions webmacs/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ def update_spell_checking(self):
self.q_profile.setSpellCheckLanguages(dicts)

def enable(self, app):
path = os.path.join(app.profiles_path(), self.name)
if not os.path.isdir(path):
os.makedirs(path)
cache_path = os.path.join(app.cache_path(), self.name)
prof_path = os.path.join(app.profiles_path(), self.name)

for path in [cache_path, prof_path]:
if not os.path.isdir(path):
os.makedirs(path)

self.q_profile.setRequestInterceptor(app.url_interceptor())

Expand All @@ -60,28 +63,28 @@ def enable(self, app):
self._scheme_handlers[handler.scheme] = h
self.q_profile.installUrlSchemeHandler(handler.scheme, h)

self.q_profile.setPersistentStoragePath(path)
self.q_profile.setPersistentStoragePath(prof_path)
self.q_profile.setPersistentCookiesPolicy(
QWebEngineProfile.ForcePersistentCookies)

if app.instance_name == "default":
session_fname = "session.json"
else:
session_fname = "session-{}.json".format(app.instance_name)
self.session_file = os.path.join(path, session_fname)
self.session_file = os.path.join(prof_path, session_fname)

self.visitedlinks \
= VisitedLinks(os.path.join(path, "visitedlinks.db"))
= VisitedLinks(os.path.join(prof_path, "visitedlinks.db"))
self.autofill \
= Autofill(PasswordDb(os.path.join(path, "autofill.db")))
= Autofill(PasswordDb(os.path.join(prof_path, "autofill.db")))
self.ignored_certs \
= IgnoredCertificates(os.path.join(path, "ignoredcerts.db"))
= IgnoredCertificates(os.path.join(prof_path, "ignoredcerts.db"))
self.bookmarks \
= Bookmarks(os.path.join(path, "bookmarks.db"))
= Bookmarks(os.path.join(prof_path, "bookmarks.db"))
self.features \
= Features(os.path.join(path, "features.db"))
= Features(os.path.join(prof_path, "features.db"))

self.q_profile.setCachePath(os.path.join(path, "cache"))
self.q_profile.setCachePath(cache_path)
self.q_profile.downloadRequested.connect(
app.download_manager().download_requested
)
Expand Down
68 changes: 68 additions & 0 deletions webmacs/xdg_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# This file is part of webmacs.
#
# webmacs 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 3 of the License, or
# (at your option) any later version.
#
# webmacs 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 webmacs. If not, see <http://www.gnu.org/licenses/>.

import os
import stat


def get_runtime_dir():
# Return whether a path matches the requirements set by
# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
def test_valid(path):
try:
stats = os.stat(path)
perms = stat.S_IMODE(stats.st_mode)

return (os.path.isdir(path) and
stats.st_uid == os.getuid() and
perms & stat.S_IRWXU == 448 and
perms & (stat.S_IRWXG | stat.S_IRWXO) == 0)

except FileNotFoundError:
return False

custom = os.getenv("XDG_RUNTIME_DIR")
if custom and test_valid(custom):
return custom
elif custom:
print("Warning: '{}' does not match the requirements for a valid "
"XDG_RUNTIME_DIR. Falling back to a default directory.\n"
"Please see the spec at https://specifications.freedesktop."
"org/basedir-spec/basedir-spec-latest.html for more information."
.format(custom,))

defaults = [
"/run/user/{}".format(os.getuid()),
"/var/run/user/{}".format(os.getuid())
]

try:
return next(filter(test_valid, defaults))
except StopIteration:
# /tmp does not really match the requirements, but it's better
# than nothing. We'll just make sure permissions are set
# properly on /tmp/.webmacs.
os.makedirs("/tmp/.webmacs")
os.chmod("/tmp/.webmacs", stat.S_IRWXU)
return "/tmp/.webmacs"


XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME",
os.path.expanduser("~/.cache"))
XDG_CONFIG_HOME = os.getenv("XDG_CONFIG_HOME",
os.path.expanduser("~/.config"))
XDG_DATA_HOME = os.getenv("XDG_DATA_HOME",
os.path.expanduser("~/.local/share"))
XDG_RUNTIME_DIR = get_runtime_dir()

0 comments on commit f0c962e

Please sign in to comment.