diff --git a/perfact/zodbsync/tests/test_sync.py b/perfact/zodbsync/tests/test_sync.py index dfdd044..5b5c9de 100644 --- a/perfact/zodbsync/tests/test_sync.py +++ b/perfact/zodbsync/tests/test_sync.py @@ -1667,6 +1667,7 @@ def addlayer(self, seqnum='00', frozen=True): with open(path, 'w') as f: f.write('base_dir = "{}"\n'.format(layer)) f.write('frozen = {}\n'.format(frozen)) + f.write('ident = "{}"\n'.format(name)) os.mkdir(os.path.join(layer, '__root__')) # Force re-reading config if hasattr(self, 'runner'): @@ -2186,3 +2187,31 @@ def test_layer_frozen(self): with open(source_fmt.format(self.repo.path)) as f: # ... content is in custom layer! assert f.read() == 'text_content' + + def test_layer_info_datafs(self): + """ + Validate the correct writing and clearing of the layer ident + in the Data.FS + """ + with self.runner.sync.tm: + self.app.manage_addProduct['OFSP'].manage_addFile(id='blob') + + with self.addlayer(frozen=True) as layer: + self.run('record', '/blob') + assert getattr(self.app.blob, 'zodbsync_layer', None) is None + # Move file to layer and check that layer info is stored in Data.FS + shutil.move( + '{}/__root__/blob'.format(self.repo.path), + '{}/__root__/blob'.format(layer), + ) + self.run('record', '/') + assert getattr(self.app.blob, 'zodbsync_layer') is not None + # Change file in Data.FS and verify that layer info is cleared + with self.runner.sync.tm: + self.app.blob.manage_edit( + filedata='text_content', + content_type='text/plain', + title='BLOB' + ) + self.run('record', '/') + assert getattr(self.app.blob, 'zodbsync_layer', None) is None diff --git a/perfact/zodbsync/zodbsync.py b/perfact/zodbsync/zodbsync.py index 85cf1c0..ae956ab 100644 --- a/perfact/zodbsync/zodbsync.py +++ b/perfact/zodbsync/zodbsync.py @@ -93,11 +93,13 @@ def mod_read(obj=None, onerrorstop=False, default_owner=None, if 'owner' in meta: del meta['owner'] + meta['zodbsync_layer'] = getattr(obj, 'zodbsync_layer', None) + return meta def mod_write(data, parent=None, obj_id=None, override=False, root=None, - default_owner=None, force_default_owner=False): + default_owner=None, force_default_owner=False, layer=None): ''' Given object data in , store the object, creating it if it was missing. With = True, this method will remove an existing object @@ -162,6 +164,9 @@ def mod_write(data, parent=None, obj_id=None, override=False, root=None, for handler in mod_implemented_handlers(obj, meta_type): handler.write(obj, d) + # Also write zodbsync layer information + obj.zodbsync_layer = layer + if temp_obj: children = temp_obj.manage_cutObjects(temp_obj.objectIds()) obj.manage_pasteObjects(children) @@ -448,7 +453,12 @@ def fs_write(self, path, data): old_data = self.fs_read(pathinfo['fspath']) # Build object - meta = {key: value for key, value in data.items() if key != 'source'} + exclude_keys = ['source', 'zodbsync_layer'] + meta = { + key: value + for key, value in data.items() + if key not in exclude_keys + } fmt = mod_format(meta) if isinstance(fmt, str): fmt = fmt.encode('utf-8') @@ -677,6 +687,11 @@ def record_obj(self, obj, path, recurse=True, skip_errors=False): raise pathinfo = self.fs_write(path, data) + path_layer = pathinfo['layers'][pathinfo['layeridx']]['ident'] + + current_layer = getattr(obj, 'zodbsync_layer', None) + if current_layer != path_layer: + obj.zodbsync_layer = path_layer if not recurse: return @@ -739,6 +754,11 @@ def _playback_path(self, pathinfo): # fspath is None if the object is to be deleted fs_data = pathinfo['fspath'] and self.fs_parse(pathinfo['fspath']) + # extend fs_data with layerinfo + if fs_data: + fs_data['zodbsync_layer'] = pathinfo['layers'][ + pathinfo['layeridx']]['ident'] + # Traverse to the object if it exists parent_obj = None obj = self.app @@ -823,6 +843,7 @@ def _playback_path(self, pathinfo): root=(obj if parent_obj is None else None), default_owner=self.default_owner, force_default_owner=self.force_default_owner, + layer=pathinfo['layers'][pathinfo['layeridx']]['ident'] ) except Exception: # If we do not want to get errors from missing