Skip to content

Commit ac58378

Browse files
dilyanpalauzovsamsonjs
authored andcommitted
cli/discover: remove/add local collections if the remote collection is deleted/created
This works when the destination backend is 'filesystem'. -- add a new parameter to storage section: implicit = ["create", "delete"] Changes cli/utils.py:save_status(): when data is None, remove the underlaying file.
1 parent f3eed32 commit ac58378

File tree

9 files changed

+52
-19
lines changed

9 files changed

+52
-19
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ Version 0.19.0
7575
use that as a reference.
7676

7777
.. _etesync-dav: https://github.com/etesync/etesync-dav
78+
- Add ``implicit`` option to storage section. It creates/deletes implicitly
79+
collections in the destinations, when new collections are created/deleted
80+
in the source. The deletion is implemented only for the "filesystem" storage.
81+
See :ref:`storage_config`.
7882

7983
Changes to SSL configuration
8084
----------------------------

docs/config.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,9 @@ Local
407407
The command will be called with the path of the deleted file.
408408
:param fileeignoreext: The file extention to ignore. It is only useful
409409
if fileext is set to the empty string. The default is ``.tmp``.
410-
:param implicit: When a new collection is created on the source,
411-
create it in the destination without asking questions, when
412-
the value is "create". When the value is "delete" and a collection
410+
:param implicit: When a new collection is created on the source, and the
411+
value is "create", create the collection in the destination without
412+
asking questions. When the value is "delete" and a collection
413413
is removed on the source, remove it in the destination. The value
414414
can be a string or an array of strings. The deletion is implemented
415415
only for the "filesystem" storage.

tests/system/cli/test_config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ def test_read_config(read_config):
6464
"instance_name": "bob_a",
6565
"implicit": [],
6666
},
67-
"bob_b": {'type': "carddav", "instance_name": "bob_b", "implicit": []},
67+
"bob_b": {
68+
"type": "carddav",
69+
"instance_name": "bob_b",
70+
"implicit": [],
71+
},
6872
}
6973

7074

tests/system/utils/test_main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_get_storage_init_args():
2222
from vdirsyncer.storage.memory import MemoryStorage
2323

2424
all, required = utils.get_storage_init_args(MemoryStorage)
25-
assert all == {"fileext", "collection", "read_only", "instance_name", "no_delete", "implicit"}
25+
assert all == {"fileext", "collection", "read_only", "instance_name", "implicit"}
2626
assert not required
2727

2828

vdirsyncer/cli/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,11 @@ def _parse_section(
122122
if "implicit" not in options:
123123
options["implicit"] = []
124124
elif isinstance(options["implicit"], str):
125-
options["implicit"] = [options['implicit']]
125+
options["implicit"] = [options["implicit"]]
126126
elif not isinstance(options["implicit"], list):
127127
raise ValueError(
128-
"`implicit` parameter must be a list, string or absent.")
128+
"`implicit` parameter must be a list, string or absent."
129+
)
129130
self._storages[name] = options
130131
elif section_type == "pair":
131132
self._pairs[name] = options

vdirsyncer/cli/discover.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from . import cli_logger
1313
from .. import exceptions
14+
from . import cli_logger
1415
from .utils import handle_collection_not_found
1516
from .utils import handle_collection_was_removed
1617
from .utils import handle_storage_init_error
@@ -144,6 +145,33 @@ async def collections_for_pair(
144145
"cache_key": cache_key,
145146
},
146147
)
148+
149+
if "from b" in (pair.collections or []):
150+
only_in_a = set(a_discovered.get_self().keys()) - set(
151+
b_discovered.get_self().keys()
152+
)
153+
if only_in_a and "delete" in pair.config_a["implicit"]:
154+
for a in only_in_a:
155+
try:
156+
handle_collection_was_removed(pair.config_a, a)
157+
save_status(status_path, pair.name, a, data_type="metadata")
158+
save_status(status_path, pair.name, a, data_type="items")
159+
except NotImplementedError as e:
160+
cli_logger.error(e)
161+
162+
if "from a" in (pair.collections or []):
163+
only_in_b = set(b_discovered.get_self().keys()) - set(
164+
a_discovered.get_self().keys()
165+
)
166+
if only_in_b and "delete" in pair.config_b["implicit"]:
167+
for b in only_in_b:
168+
try:
169+
handle_collection_was_removed(pair.config_b, b)
170+
save_status(status_path, pair.name, b, data_type="metadata")
171+
save_status(status_path, pair.name, b, data_type="items")
172+
except NotImplementedError as e:
173+
cli_logger.error(e)
174+
147175
return rv
148176

149177

vdirsyncer/cli/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ def save_status(
242242
data: dict[str, Any],
243243
collection: str | None = None,
244244
) -> None:
245+
assert data_type is not None
245246
status_name = get_status_name(pair, collection)
246247
path = expand_path(os.path.join(base_path, status_name)) + "." + data_type
247248
prepare_status_path(path)
@@ -370,7 +371,8 @@ async def handle_collection_not_found(config, collection, e=None):
370371
)
371372

372373
if "create" in config["implicit"] or click.confirm(
373-
"Should vdirsyncer attempt to create it?"):
374+
"Should vdirsyncer attempt to create it?"
375+
):
374376
storage_type = config["type"]
375377
cls, config = storage_class_from_config(config)
376378
config["collection"] = collection

vdirsyncer/storage/base.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,12 @@ def __init__(
8585
self,
8686
instance_name=None,
8787
read_only=None,
88-
no_delete=None,
8988
collection=None,
90-
implicit=None
89+
implicit=None,
9190
):
9291
if read_only is None:
9392
read_only = self.read_only
94-
if implicit is None:
95-
self.implicit = []
96-
elif isinstance(implicit, str):
97-
self.implicit = [implicit]
98-
else:
99-
self.implicit = implicit
93+
self.implicit = implicit # unused from within the Storage classes
10094
if self.read_only and not read_only:
10195
raise exceptions.UserError("This storage can only be read-only.")
10296
self.read_only = bool(read_only)
@@ -146,14 +140,14 @@ async def create_collection(cls, collection, **kwargs):
146140

147141
@classmethod
148142
def delete_collection(cls, collection, **kwargs):
149-
'''
143+
"""
150144
Delete the specified collection and return the new arguments.
151145
152146
``collection=None`` means the arguments are already pointing to a
153147
possible collection location.
154148
155149
The returned args should contain the collection name, for UI purposes.
156-
'''
150+
"""
157151
raise NotImplementedError()
158152

159153
def __repr__(self):

vdirsyncer/storage/filesystem.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ async def create_collection(cls, collection, **kwargs):
8585
@classmethod
8686
def delete_collection(cls, collection, **kwargs):
8787
kwargs = dict(kwargs)
88-
path = kwargs['path']
88+
path = kwargs["path"]
8989

9090
if collection is not None:
9191
path = os.path.join(path, collection)

0 commit comments

Comments
 (0)