|
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, is_versioned_file, save_to_file |
| 28 | +from .utils import cleanup_tmp_dir, filter_files, is_path_in_scope, save_to_file, is_versioned_file |
29 | 29 | from typing import List, Optional, Union |
30 | 30 |
|
31 | 31 | # status = download_project_async(...) |
@@ -261,12 +261,18 @@ def _cleanup_failed_download(mergin_project: MerginProject = None): |
261 | 261 | return dest_path |
262 | 262 |
|
263 | 263 |
|
264 | | -def download_project_async(mc, project_path, directory, project_version=None): |
| 264 | +def download_project_async(mc, project_path, directory, project_version=None, include=None, exclude=None): |
265 | 265 | """ |
266 | 266 | Starts project download in background and returns handle to the pending project download. |
267 | 267 | Using that object it is possible to watch progress or cancel the ongoing work. |
| 268 | +
|
| 269 | + `include`/`exclude` are optional lists of glob patterns (matched against each file's project |
| 270 | + path, e.g. "media/*" or "*.gpkg") to only download a subset of the project's files. They are |
| 271 | + mutually exclusive. |
268 | 272 | """ |
269 | 273 |
|
| 274 | + if include and exclude: |
| 275 | + raise ClientError("Cannot use both include and exclude filters at the same time") |
270 | 276 | if "/" not in project_path: |
271 | 277 | raise ClientError("Project name needs to be fully qualified, e.g. <username>/<projectname>") |
272 | 278 | if os.path.exists(directory): |
@@ -295,6 +301,12 @@ def download_project_async(mc, project_path, directory, project_version=None): |
295 | 301 |
|
296 | 302 | mp.log.info(f"got project info. version {version}") |
297 | 303 |
|
| 304 | + # keep only the files matching the filter (if any) |
| 305 | + project_info["files"] = filter_files(project_info["files"], include=include, exclude=exclude) |
| 306 | + # persisted once since it must never change again for this checkout |
| 307 | + if include or exclude: |
| 308 | + mp.write_file_filter({"include": include, "exclude": exclude}) |
| 309 | + |
298 | 310 | # prepare download |
299 | 311 | update_tasks = [] # stuff to do at the end of download |
300 | 312 | for file in project_info["files"]: |
@@ -544,6 +556,9 @@ def pull_project_async(mc, directory) -> Optional[PullJob]: |
544 | 556 | mp.log.info("--- pull aborted") |
545 | 557 | raise |
546 | 558 |
|
| 559 | + file_filter = mp.file_filter() |
| 560 | + delta.changes = [c for c in delta.changes if is_path_in_scope(c.path, **file_filter)] |
| 561 | + |
547 | 562 | mp.log.info(f"got project versions: local version {local_version} / server version {server_version}") |
548 | 563 |
|
549 | 564 | if local_version == server_version: |
@@ -767,6 +782,9 @@ def pull_project_finalize(job: PullJob): |
767 | 782 | cleanup_tmp_dir(job.mp, job.tmp_dir) # delete our temporary dir and all its content |
768 | 783 | raise ClientError("Failed to apply pull actions: " + str(e)) |
769 | 784 |
|
| 785 | + # keep only in-scope files in the metadata we're about to persist |
| 786 | + job.project_info["files"] = filter_files(job.project_info["files"], **job.mp.file_filter()) |
| 787 | + |
770 | 788 | job.mp.update_metadata(job.project_info) |
771 | 789 |
|
772 | 790 | if job.mp.has_unfinished_pull(): |
|
0 commit comments