Merge pull request #491 from exarkun/consolidate-magicfolder-config

Consolidate magic-folder configuration-loading code.
This commit is contained in:
Jean-Paul Calderone 2018-04-23 15:50:26 -04:00 committed by GitHub
commit c5f95f026e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 25 deletions

View File

@ -593,23 +593,7 @@ class _Client(node.Node, pollmixin.PollMixin):
for (name, mf_config) in magic_folders.items(): for (name, mf_config) in magic_folders.items():
self.log("Starting magic_folder '{}'".format(name)) self.log("Starting magic_folder '{}'".format(name))
db_filename = os.path.join(self.basedir, "private", "magicfolder_{}.sqlite".format(name)) s = magic_folder.MagicFolder.from_config(self, name, mf_config)
local_dir_config = mf_config['directory']
try:
poll_interval = int(mf_config["poll_interval"])
except ValueError:
raise ValueError("'poll_interval' option must be an int")
s = magic_folder.MagicFolder(
client=self,
upload_dircap=mf_config["upload_dircap"],
collective_dircap=mf_config["collective_dircap"],
local_path_u=abspath_expanduser_unicode(local_dir_config, base=self.basedir),
dbfile=abspath_expanduser_unicode(db_filename),
umask=self.get_config("magic_folder", "download.umask", 0077),
name=name,
downloader_delay=poll_interval,
)
self._magic_folders[name] = s self._magic_folders[name] = s
s.setServiceParent(self) s.setServiceParent(self)
s.startService() s.startService()

View File

@ -18,7 +18,12 @@ from zope.interface import Interface, Attribute, implementer
from allmydata.util import fileutil, configutil, yamlutil from allmydata.util import fileutil, configutil, yamlutil
from allmydata.interfaces import IDirectoryNode from allmydata.interfaces import IDirectoryNode
from allmydata.util import log from allmydata.util import log
from allmydata.util.fileutil import precondition_abspath, get_pathinfo, ConflictError from allmydata.util.fileutil import (
precondition_abspath,
get_pathinfo,
ConflictError,
abspath_expanduser_unicode,
)
from allmydata.util.assertutil import precondition, _assert from allmydata.util.assertutil import precondition, _assert
from allmydata.util.deferredutil import HookMixin from allmydata.util.deferredutil import HookMixin
from allmydata.util.progress import PercentProgress from allmydata.util.progress import PercentProgress
@ -30,6 +35,9 @@ from allmydata.immutable.upload import FileName, Data
from allmydata import magicfolderdb, magicpath from allmydata import magicfolderdb, magicpath
# Mask off all non-owner permissions for magic-folders files by default.
_DEFAULT_DOWNLOAD_UMASK = 0o077
IN_EXCL_UNLINK = 0x04000000L IN_EXCL_UNLINK = 0x04000000L
def get_inotify_module(): def get_inotify_module():
@ -119,6 +127,7 @@ def load_magic_folders(node_directory):
old-style to new-style config (but WILL read old-style config and old-style to new-style config (but WILL read old-style config and
return in the same way as if it was new-style). return in the same way as if it was new-style).
:param node_directory: path where node data is stored
:returns: dict mapping magic-folder-name to its config (also a dict) :returns: dict mapping magic-folder-name to its config (also a dict)
""" """
yaml_fname = os.path.join(node_directory, u"private", u"magic_folders.yaml") yaml_fname = os.path.join(node_directory, u"private", u"magic_folders.yaml")
@ -156,11 +165,17 @@ def load_magic_folders(node_directory):
) )
) )
if config.has_option("magic_folder", "download.umask"):
umask = int(config.get("magic_folder", "download.umask"), 8)
else:
umask = _DEFAULT_DOWNLOAD_UMASK
folders[u"default"] = { folders[u"default"] = {
u"directory": directory, u"directory": directory,
u"upload_dircap": fileutil.read(up_fname), u"upload_dircap": fileutil.read(up_fname),
u"collective_dircap": fileutil.read(coll_fname), u"collective_dircap": fileutil.read(coll_fname),
u"poll_interval": interval, u"poll_interval": interval,
u"umask": umask,
} }
else: else:
# without any YAML file AND no local.directory option it's # without any YAML file AND no local.directory option it's
@ -212,6 +227,12 @@ def load_magic_folders(node_directory):
if isinstance(mf_config[k], unicode): if isinstance(mf_config[k], unicode):
mf_config[k] = mf_config[k].encode('ascii') mf_config[k] = mf_config[k].encode('ascii')
if not isinstance(
mf_config.setdefault(u"umask", _DEFAULT_DOWNLOAD_UMASK),
int,
):
raise Exception("magic-folder download umask must be an integer")
return folders return folders
@ -228,6 +249,43 @@ def save_magic_folders(node_directory, folders):
class MagicFolder(service.MultiService): class MagicFolder(service.MultiService):
@classmethod
def from_config(cls, client_node, name, config):
"""
Create a ``MagicFolder`` from a client node and magic-folder
configuration.
:param _Client client_node: The client node the magic-folder is
attached to.
:param dict config: Magic-folder configuration like that in the list
returned by ``load_magic_folders``.
"""
db_filename = os.path.join(
client_node.basedir,
"private",
"magicfolder_{}.sqlite".format(name),
)
local_dir_config = config['directory']
try:
poll_interval = int(config["poll_interval"])
except ValueError:
raise ValueError("'poll_interval' option must be an int")
return cls(
client=client_node,
upload_dircap=config["upload_dircap"],
collective_dircap=config["collective_dircap"],
local_path_u=abspath_expanduser_unicode(
local_dir_config,
base=client_node.basedir,
),
dbfile=abspath_expanduser_unicode(db_filename),
umask=config["umask"],
name=name,
downloader_delay=poll_interval,
)
def __init__(self, client, upload_dircap, collective_dircap, local_path_u, dbfile, umask, def __init__(self, client, upload_dircap, collective_dircap, local_path_u, dbfile, umask,
name, uploader_delay=1.0, clock=None, downloader_delay=60): name, uploader_delay=1.0, clock=None, downloader_delay=60):
precondition_abspath(local_path_u) precondition_abspath(local_path_u)

View File

@ -401,11 +401,18 @@ class Basic(testutil.ReallyEqualMixin, testutil.NonASCIIPathMixin, unittest.Test
_check("helper.furl = pb://blah\n", "pb://blah") _check("helper.furl = pb://blah\n", "pb://blah")
def test_create_magic_folder_service(self): def test_create_magic_folder_service(self):
class MockMagicFolder(service.MultiService): boom = False
class Boom(Exception):
pass
class MockMagicFolder(allmydata.frontends.magic_folder.MagicFolder):
name = 'magic-folder' name = 'magic-folder'
def __init__(self, client, upload_dircap, collective_dircap, local_path_u, dbfile, umask, name, def __init__(self, client, upload_dircap, collective_dircap, local_path_u, dbfile, umask, name,
inotify=None, uploader_delay=1.0, clock=None, downloader_delay=3): inotify=None, uploader_delay=1.0, clock=None, downloader_delay=3):
if boom:
raise Boom()
service.MultiService.__init__(self) service.MultiService.__init__(self)
self.client = client self.client = client
self._umask = umask self._umask = umask
@ -415,6 +422,12 @@ class Basic(testutil.ReallyEqualMixin, testutil.NonASCIIPathMixin, unittest.Test
self.dbfile = dbfile self.dbfile = dbfile
self.inotify = inotify self.inotify = inotify
def startService(self):
self.running = True
def stopService(self):
self.running = False
def ready(self): def ready(self):
pass pass
@ -461,12 +474,8 @@ class Basic(testutil.ReallyEqualMixin, testutil.NonASCIIPathMixin, unittest.Test
self.failUnless(magicfolder.inotify is None, magicfolder.inotify) self.failUnless(magicfolder.inotify is None, magicfolder.inotify)
self.failUnless(magicfolder.running) self.failUnless(magicfolder.running)
class Boom(Exception): # See above.
pass boom = True
def BoomMagicFolder(client, upload_dircap, collective_dircap, local_path_u, dbfile, name,
umask, inotify=None, uploader_delay=1.0, clock=None, downloader_delay=3):
raise Boom()
self.patch(allmydata.frontends.magic_folder, 'MagicFolder', BoomMagicFolder)
basedir2 = "test_client.Basic.test_create_magic_folder_service2" basedir2 = "test_client.Basic.test_create_magic_folder_service2"
os.mkdir(basedir2) os.mkdir(basedir2)

View File

@ -65,6 +65,7 @@ class NewConfigUtilTests(unittest.TestCase):
def test_load(self): def test_load(self):
folders = magic_folder.load_magic_folders(self.basedir) folders = magic_folder.load_magic_folders(self.basedir)
self.assertEqual(['default'], list(folders.keys())) self.assertEqual(['default'], list(folders.keys()))
self.assertEqual(folders['default'][u'umask'], 0o077)
def test_both_styles_of_config(self): def test_both_styles_of_config(self):
os.unlink(join(self.basedir, u"private", u"magic_folders.yaml")) os.unlink(join(self.basedir, u"private", u"magic_folders.yaml"))
@ -115,6 +116,23 @@ class NewConfigUtilTests(unittest.TestCase):
str(ctx.exception) str(ctx.exception)
) )
def test_wrong_umask_obj(self):
"""
If a umask is given for a magic-folder that is not an integer, an
exception is raised.
"""
self.folders[u"default"][u"umask"] = "0077"
yaml_fname = join(self.basedir, u"private", u"magic_folders.yaml")
with open(yaml_fname, "w") as f:
f.write(yamlutil.safe_dump({u"magic-folders": self.folders}))
with self.assertRaises(Exception) as ctx:
magic_folder.load_magic_folders(self.basedir)
self.assertIn(
"umask must be an integer",
str(ctx.exception)
)
def test_wrong_sub_obj(self): def test_wrong_sub_obj(self):
yaml_fname = join(self.basedir, u"private", u"magic_folders.yaml") yaml_fname = join(self.basedir, u"private", u"magic_folders.yaml")
with open(yaml_fname, "w") as f: with open(yaml_fname, "w") as f: