|
25 | 25 | from .common import CHUNK_SIZE, ClientError, DeltaChangeType, PullActionType |
26 | 26 | from .models import ProjectDelta, ProjectDeltaChange, PullAction |
27 | 27 | from .merginproject import MerginProject |
28 | | -from .utils import cleanup_tmp_dir, save_to_file |
| 28 | +from .utils import cleanup_tmp_dir, filter_files, path_matches_filter, save_to_file, validates_file_filter |
29 | 29 | from typing import List, Optional |
30 | 30 |
|
31 | 31 | # status = download_project_async(...) |
@@ -242,10 +242,15 @@ def _cleanup_failed_download(mergin_project: MerginProject = None): |
242 | 242 | return dest_path |
243 | 243 |
|
244 | 244 |
|
245 | | -def download_project_async(mc, project_path, directory, project_version=None): |
| 245 | +@validates_file_filter |
| 246 | +def download_project_async(mc, project_path, directory, project_version=None, include=None, exclude=None): |
246 | 247 | """ |
247 | 248 | Starts project download in background and returns handle to the pending project download. |
248 | 249 | Using that object it is possible to watch progress or cancel the ongoing work. |
| 250 | +
|
| 251 | + `include`/`exclude` are optional lists of glob patterns (matched against each file's project |
| 252 | + path, e.g. "media/*" or "*.gpkg") to only download a subset of the project's files. They are |
| 253 | + mutually exclusive. |
249 | 254 | """ |
250 | 255 |
|
251 | 256 | if "/" not in project_path: |
@@ -276,6 +281,11 @@ def download_project_async(mc, project_path, directory, project_version=None): |
276 | 281 |
|
277 | 282 | mp.log.info(f"got project info. version {version}") |
278 | 283 |
|
| 284 | + # keep only the files matching the filter (if any) |
| 285 | + project_info["files"] = filter_files(project_info["files"], include=include, exclude=exclude) |
| 286 | + if include or exclude: |
| 287 | + project_info["file_filter"] = {"include": include, "exclude": exclude} |
| 288 | + |
279 | 289 | # prepare download |
280 | 290 | update_tasks = [] # stuff to do at the end of download |
281 | 291 | for file in project_info["files"]: |
@@ -525,6 +535,9 @@ def pull_project_async(mc, directory) -> Optional[PullJob]: |
525 | 535 | mp.log.info("--- pull aborted") |
526 | 536 | raise |
527 | 537 |
|
| 538 | + file_filter = mp.file_filter() |
| 539 | + delta.changes = [c for c in delta.changes if path_matches_filter(c.path, **file_filter)] |
| 540 | + |
528 | 541 | mp.log.info(f"got project versions: local version {local_version} / server version {server_version}") |
529 | 542 |
|
530 | 543 | if local_version == server_version: |
@@ -748,6 +761,14 @@ def pull_project_finalize(job: PullJob): |
748 | 761 | cleanup_tmp_dir(job.mp, job.tmp_dir) # delete our temporary dir and all its content |
749 | 762 | raise ClientError("Failed to apply pull actions: " + str(e)) |
750 | 763 |
|
| 764 | + file_filter = job.mp.file_filter() |
| 765 | + job.project_info["files"] = filter_files(job.project_info["files"], **file_filter) |
| 766 | + # keep the sparse checkout: re-apply the filter this project was downloaded with, |
| 767 | + # since job.project_info is a fresh, unfiltered response from the server |
| 768 | + # and update_metadata() replaces the whole metadata dict rather than merging into it |
| 769 | + if file_filter["include"] or file_filter["exclude"]: |
| 770 | + job.project_info["file_filter"] = file_filter |
| 771 | + |
751 | 772 | job.mp.update_metadata(job.project_info) |
752 | 773 |
|
753 | 774 | if job.mp.has_unfinished_pull(): |
|
0 commit comments